From 784bf88b0d0005b771e1bab5adfd6094a3693494 Mon Sep 17 00:00:00 2001 From: Daniel Hiltgen Date: Tue, 18 Jun 2024 16:22:47 -0700 Subject: [PATCH 001/384] Wire up windows AMD driver reporting This seems to be ROCm version, not actually driver version, but it may be useful for toggling logic for VRAM reporting in the future --- gpu/amd_hip_windows.go | 5 ++--- gpu/amd_windows.go | 17 +++++++---------- 2 files changed, 9 insertions(+), 13 deletions(-) diff --git a/gpu/amd_hip_windows.go b/gpu/amd_hip_windows.go index 8572a24c5..2586278c8 100644 --- a/gpu/amd_hip_windows.go +++ b/gpu/amd_hip_windows.go @@ -84,9 +84,8 @@ func (hl *HipLib) AMDDriverVersion() (driverMajor, driverMinor int, err error) { } slog.Debug("hipDriverGetVersion", "version", version) - // TODO - this isn't actually right, but the docs claim hipDriverGetVersion isn't accurate anyway... - driverMajor = version / 1000 - driverMinor = (version - (driverMajor * 1000)) / 10 + driverMajor = version / 10000000 + driverMinor = (version - (driverMajor * 10000000)) / 100000 return driverMajor, driverMinor, nil } diff --git a/gpu/amd_windows.go b/gpu/amd_windows.go index 21585277a..0c76f6b9d 100644 --- a/gpu/amd_windows.go +++ b/gpu/amd_windows.go @@ -35,12 +35,11 @@ func AMDGetGPUInfo() []RocmGPUInfo { } defer hl.Release() - // TODO - this reports incorrect version information, so omitting for now - // driverMajor, driverMinor, err := hl.AMDDriverVersion() - // if err != nil { - // // For now this is benign, but we may eventually need to fail compatibility checks - // slog.Debug("error looking up amd driver version", "error", err) - // } + driverMajor, driverMinor, err := hl.AMDDriverVersion() + if err != nil { + // For now this is benign, but we may eventually need to fail compatibility checks + slog.Debug("error looking up amd driver version", "error", err) + } // Note: the HIP library automatically handles subsetting to any HIP_VISIBLE_DEVICES the user specified count := hl.HipGetDeviceCount() @@ -131,10 +130,8 @@ func AMDGetGPUInfo() []RocmGPUInfo { MinimumMemory: rocmMinimumMemory, Name: name, Compute: gfx, - - // TODO - this information isn't accurate on windows, so don't report it until we find the right way to retrieve - // DriverMajor: driverMajor, - // DriverMinor: driverMinor, + DriverMajor: driverMajor, + DriverMinor: driverMinor, }, index: i, } From 97c9e11768292c8f2732e2f4c9cde72a604c936b Mon Sep 17 00:00:00 2001 From: Daniel Hiltgen Date: Fri, 28 Jun 2024 09:57:10 -0700 Subject: [PATCH 002/384] Switch use_mmap to a pointer type This uses nil as undefined for a cleaner implementation. --- api/types.go | 105 ++++++++++++++++------------------------------ api/types_test.go | 40 ++++++++++-------- llm/server.go | 11 ++--- 3 files changed, 63 insertions(+), 93 deletions(-) diff --git a/api/types.go b/api/types.go index 95ed5d37e..3b67d57a3 100644 --- a/api/types.go +++ b/api/types.go @@ -159,49 +159,18 @@ type Options struct { // Runner options which must be set when the model is loaded into memory type Runner struct { - UseNUMA bool `json:"numa,omitempty"` - NumCtx int `json:"num_ctx,omitempty"` - NumBatch int `json:"num_batch,omitempty"` - NumGPU int `json:"num_gpu,omitempty"` - MainGPU int `json:"main_gpu,omitempty"` - LowVRAM bool `json:"low_vram,omitempty"` - F16KV bool `json:"f16_kv,omitempty"` - LogitsAll bool `json:"logits_all,omitempty"` - VocabOnly bool `json:"vocab_only,omitempty"` - UseMMap TriState `json:"use_mmap,omitempty"` - UseMLock bool `json:"use_mlock,omitempty"` - NumThread int `json:"num_thread,omitempty"` -} - -type TriState int - -const ( - TriStateUndefined TriState = -1 - TriStateFalse TriState = 0 - TriStateTrue TriState = 1 -) - -func (b *TriState) UnmarshalJSON(data []byte) error { - var v bool - if err := json.Unmarshal(data, &v); err != nil { - return err - } - if v { - *b = TriStateTrue - } - *b = TriStateFalse - return nil -} - -func (b *TriState) MarshalJSON() ([]byte, error) { - if *b == TriStateUndefined { - return nil, nil - } - var v bool - if *b == TriStateTrue { - v = true - } - return json.Marshal(v) + UseNUMA bool `json:"numa,omitempty"` + NumCtx int `json:"num_ctx,omitempty"` + NumBatch int `json:"num_batch,omitempty"` + NumGPU int `json:"num_gpu,omitempty"` + MainGPU int `json:"main_gpu,omitempty"` + LowVRAM bool `json:"low_vram,omitempty"` + F16KV bool `json:"f16_kv,omitempty"` + LogitsAll bool `json:"logits_all,omitempty"` + VocabOnly bool `json:"vocab_only,omitempty"` + UseMMap *bool `json:"use_mmap,omitempty"` + UseMLock bool `json:"use_mlock,omitempty"` + NumThread int `json:"num_thread,omitempty"` } // EmbeddingRequest is the request passed to [Client.Embeddings]. @@ -437,19 +406,6 @@ func (opts *Options) FromMap(m map[string]interface{}) error { continue } - if reflect.PointerTo(field.Type()) == reflect.TypeOf((*TriState)(nil)) { - val, ok := val.(bool) - if !ok { - return fmt.Errorf("option %q must be of type boolean", key) - } - if val { - field.SetInt(int64(TriStateTrue)) - } else { - field.SetInt(int64(TriStateFalse)) - } - continue - } - switch field.Kind() { case reflect.Int: switch t := val.(type) { @@ -496,6 +452,17 @@ func (opts *Options) FromMap(m map[string]interface{}) error { slice[i] = str } field.Set(reflect.ValueOf(slice)) + case reflect.Pointer: + var b bool + if field.Type() == reflect.TypeOf(&b) { + val, ok := val.(bool) + if !ok { + return fmt.Errorf("option %q must be of type boolean", key) + } + field.Set(reflect.ValueOf(&val)) + } else { + return fmt.Errorf("unknown type loading config params: %v %v", field.Kind(), field.Type()) + } default: return fmt.Errorf("unknown type loading config params: %v", field.Kind()) } @@ -538,7 +505,7 @@ func DefaultOptions() Options { LowVRAM: false, F16KV: true, UseMLock: false, - UseMMap: TriStateUndefined, + UseMMap: nil, UseNUMA: false, }, } @@ -608,19 +575,6 @@ func FormatParams(params map[string][]string) (map[string]interface{}, error) { } else { field := valueOpts.FieldByName(opt.Name) if field.IsValid() && field.CanSet() { - if reflect.PointerTo(field.Type()) == reflect.TypeOf((*TriState)(nil)) { - boolVal, err := strconv.ParseBool(vals[0]) - if err != nil { - return nil, fmt.Errorf("invalid bool value %s", vals) - } - if boolVal { - out[key] = TriStateTrue - } else { - out[key] = TriStateFalse - } - continue - } - switch field.Kind() { case reflect.Float32: floatVal, err := strconv.ParseFloat(vals[0], 32) @@ -648,6 +602,17 @@ func FormatParams(params map[string][]string) (map[string]interface{}, error) { case reflect.Slice: // TODO: only string slices are supported right now out[key] = vals + case reflect.Pointer: + var b bool + if field.Type() == reflect.TypeOf(&b) { + boolVal, err := strconv.ParseBool(vals[0]) + if err != nil { + return nil, fmt.Errorf("invalid bool value %s", vals) + } + out[key] = &boolVal + } else { + return nil, fmt.Errorf("unknown type %s for %s", field.Kind(), key) + } default: return nil, fmt.Errorf("unknown type %s for %s", field.Kind(), key) } diff --git a/api/types_test.go b/api/types_test.go index 8b6c60c62..c60ed90e0 100644 --- a/api/types_test.go +++ b/api/types_test.go @@ -108,25 +108,27 @@ func TestDurationMarshalUnmarshal(t *testing.T) { } func TestUseMmapParsingFromJSON(t *testing.T) { + tr := true + fa := false tests := []struct { name string req string - exp TriState + exp *bool }{ { name: "Undefined", req: `{ }`, - exp: TriStateUndefined, + exp: nil, }, { name: "True", req: `{ "use_mmap": true }`, - exp: TriStateTrue, + exp: &tr, }, { name: "False", req: `{ "use_mmap": false }`, - exp: TriStateFalse, + exp: &fa, }, } @@ -144,50 +146,52 @@ func TestUseMmapParsingFromJSON(t *testing.T) { } func TestUseMmapFormatParams(t *testing.T) { + tr := true + fa := false tests := []struct { name string req map[string][]string - exp TriState + exp *bool err error }{ { name: "True", req: map[string][]string{ - "use_mmap": []string{"true"}, + "use_mmap": {"true"}, }, - exp: TriStateTrue, + exp: &tr, err: nil, }, { name: "False", req: map[string][]string{ - "use_mmap": []string{"false"}, + "use_mmap": {"false"}, }, - exp: TriStateFalse, + exp: &fa, err: nil, }, { name: "Numeric True", req: map[string][]string{ - "use_mmap": []string{"1"}, + "use_mmap": {"1"}, }, - exp: TriStateTrue, + exp: &tr, err: nil, }, { name: "Numeric False", req: map[string][]string{ - "use_mmap": []string{"0"}, + "use_mmap": {"0"}, }, - exp: TriStateFalse, + exp: &fa, err: nil, }, { name: "invalid string", req: map[string][]string{ - "use_mmap": []string{"foo"}, + "use_mmap": {"foo"}, }, - exp: TriStateUndefined, + exp: nil, err: fmt.Errorf("invalid bool value [foo]"), }, } @@ -195,11 +199,11 @@ func TestUseMmapFormatParams(t *testing.T) { for _, test := range tests { t.Run(test.name, func(t *testing.T) { resp, err := FormatParams(test.req) - require.Equal(t, err, test.err) + require.Equal(t, test.err, err) respVal, ok := resp["use_mmap"] - if test.exp != TriStateUndefined { + if test.exp != nil { assert.True(t, ok, "resp: %v", resp) - assert.Equal(t, test.exp, respVal) + assert.Equal(t, *test.exp, *respVal.(*bool)) } }) } diff --git a/llm/server.go b/llm/server.go index 61346069e..821f6efdc 100644 --- a/llm/server.go +++ b/llm/server.go @@ -208,7 +208,8 @@ func NewLlamaServer(gpus gpu.GpuInfoList, model string, ggml *GGML, adapters, pr if g.Library == "metal" && uint64(opts.NumGPU) > 0 && uint64(opts.NumGPU) < ggml.KV().BlockCount()+1 { - opts.UseMMap = api.TriStateFalse + opts.UseMMap = new(bool) + *opts.UseMMap = false } } @@ -219,10 +220,10 @@ func NewLlamaServer(gpus gpu.GpuInfoList, model string, ggml *GGML, adapters, pr // Windows CUDA should not use mmap for best performance // Linux with a model larger than free space, mmap leads to thrashing // For CPU loads we want the memory to be allocated, not FS cache - if (runtime.GOOS == "windows" && gpus[0].Library == "cuda" && opts.UseMMap == api.TriStateUndefined) || - (runtime.GOOS == "linux" && systemFreeMemory < estimate.TotalSize && opts.UseMMap == api.TriStateUndefined) || - (gpus[0].Library == "cpu" && opts.UseMMap == api.TriStateUndefined) || - opts.UseMMap == api.TriStateFalse { + if (runtime.GOOS == "windows" && gpus[0].Library == "cuda" && opts.UseMMap == nil) || + (runtime.GOOS == "linux" && systemFreeMemory < estimate.TotalSize && opts.UseMMap == nil) || + (gpus[0].Library == "cpu" && opts.UseMMap == nil) || + (opts.UseMMap != nil && !*opts.UseMMap) { params = append(params, "--no-mmap") } From 3f0b309ad4c49c0d87839e50fe6a46163902aba0 Mon Sep 17 00:00:00 2001 From: Michael Yang Date: Mon, 10 Jun 2024 08:47:13 -0700 Subject: [PATCH 003/384] remove ManifestV2 --- server/images.go | 17 +++++------------ server/manifest.go | 20 +++++++++++--------- server/manifest_test.go | 2 +- 3 files changed, 17 insertions(+), 22 deletions(-) diff --git a/server/images.go b/server/images.go index e949fb18a..447a63a69 100644 --- a/server/images.go +++ b/server/images.go @@ -135,13 +135,6 @@ type Message struct { Content string `json:"content"` } -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"` @@ -160,7 +153,7 @@ type RootFS struct { DiffIDs []string `json:"diff_ids"` } -func GetManifest(mp ModelPath) (*ManifestV2, string, error) { +func GetManifest(mp ModelPath) (*Manifest, string, error) { fp, err := mp.GetManifestPath() if err != nil { return nil, "", err @@ -170,7 +163,7 @@ func GetManifest(mp ModelPath) (*ManifestV2, string, error) { return nil, "", err } - var manifest *ManifestV2 + var manifest *Manifest bts, err := os.ReadFile(fp) if err != nil { @@ -822,7 +815,7 @@ func PushModel(ctx context.Context, name string, regOpts *registryOptions, fn fu func PullModel(ctx context.Context, name string, regOpts *registryOptions, fn func(api.ProgressResponse)) error { mp := ParseModelPath(name) - var manifest *ManifestV2 + var manifest *Manifest var err error var noprune string @@ -929,7 +922,7 @@ func PullModel(ctx context.Context, name string, regOpts *registryOptions, fn fu return nil } -func pullModelManifest(ctx context.Context, mp ModelPath, regOpts *registryOptions) (*ManifestV2, error) { +func pullModelManifest(ctx context.Context, mp ModelPath, regOpts *registryOptions) (*Manifest, error) { requestURL := mp.BaseURL().JoinPath("v2", mp.GetNamespaceRepository(), "manifests", mp.Tag) headers := make(http.Header) @@ -940,7 +933,7 @@ func pullModelManifest(ctx context.Context, mp ModelPath, regOpts *registryOptio } defer resp.Body.Close() - var m *ManifestV2 + var m *Manifest if err := json.NewDecoder(resp.Body).Decode(&m); err != nil { return nil, err } diff --git a/server/manifest.go b/server/manifest.go index 61dd1ab4e..726bb48d8 100644 --- a/server/manifest.go +++ b/server/manifest.go @@ -14,7 +14,10 @@ import ( ) type Manifest struct { - ManifestV2 + SchemaVersion int `json:"schemaVersion"` + MediaType string `json:"mediaType"` + Config *Layer `json:"config"` + Layers []*Layer `json:"layers"` filepath string fi os.FileInfo @@ -66,7 +69,7 @@ func ParseNamedManifest(n model.Name) (*Manifest, error) { p := filepath.Join(manifests, n.Filepath()) - var m ManifestV2 + var m Manifest f, err := os.Open(p) if err != nil { return nil, err @@ -83,12 +86,11 @@ func ParseNamedManifest(n model.Name) (*Manifest, error) { return nil, err } - return &Manifest{ - ManifestV2: m, - filepath: p, - fi: fi, - digest: fmt.Sprintf("%x", sha256sum.Sum(nil)), - }, nil + m.filepath = p + m.fi = fi + m.digest = fmt.Sprintf("%x", sha256sum.Sum(nil)) + + return &m, nil } func WriteManifest(name model.Name, config *Layer, layers []*Layer) error { @@ -108,7 +110,7 @@ func WriteManifest(name model.Name, config *Layer, layers []*Layer) error { } defer f.Close() - m := ManifestV2{ + m := Manifest{ SchemaVersion: 2, MediaType: "application/vnd.docker.distribution.manifest.v2+json", Config: config, diff --git a/server/manifest_test.go b/server/manifest_test.go index ceee31d88..ca6c3d2e9 100644 --- a/server/manifest_test.go +++ b/server/manifest_test.go @@ -25,7 +25,7 @@ func createManifest(t *testing.T, path, name string) { } defer f.Close() - if err := json.NewEncoder(f).Encode(ManifestV2{}); err != nil { + if err := json.NewEncoder(f).Encode(Manifest{}); err != nil { t.Fatal(err) } } From 58e3fff311f9e7abec20cdfe20fa43958e447aeb Mon Sep 17 00:00:00 2001 From: Michael Yang Date: Mon, 10 Jun 2024 14:54:42 -0700 Subject: [PATCH 004/384] rename templates to template --- server/images.go | 26 ++- server/model.go | 4 +- server/prompt.go | 18 +- server/prompt_test.go | 15 +- server/routes.go | 26 ++- {templates => template}/alfred.gotmpl | 0 {templates => template}/alpaca.gotmpl | 0 {templates => template}/chatml.gotmpl | 0 {templates => template}/chatqa.gotmpl | 0 .../codellama-70b-instruct.gotmpl | 0 .../falcon-instruct.gotmpl | 0 {templates => template}/gemma-instruct.gotmpl | 0 .../granite-instruct.gotmpl | 0 {templates => template}/index.json | 0 {templates => template}/llama2-chat.gotmpl | 0 .../llama3-instruct.gotmpl | 0 {templates => template}/magicoder.gotmpl | 0 .../mistral-instruct.gotmpl | 0 {templates => template}/openchat.gotmpl | 0 {templates => template}/phi-3.gotmpl | 0 {templates => template}/solar-instruct.gotmpl | 0 .../starcoder2-instruct.gotmpl | 0 template/template.go | 158 ++++++++++++++++++ template/template_test.go | 89 ++++++++++ .../testdata/templates.jsonl | 0 {templates => template}/vicuna.gotmpl | 0 {templates => template}/zephyr.gotmpl | 0 templates/template.go | 70 -------- templates/template_test.go | 59 ------- 29 files changed, 301 insertions(+), 164 deletions(-) rename {templates => template}/alfred.gotmpl (100%) rename {templates => template}/alpaca.gotmpl (100%) rename {templates => template}/chatml.gotmpl (100%) rename {templates => template}/chatqa.gotmpl (100%) rename {templates => template}/codellama-70b-instruct.gotmpl (100%) rename {templates => template}/falcon-instruct.gotmpl (100%) rename {templates => template}/gemma-instruct.gotmpl (100%) rename {templates => template}/granite-instruct.gotmpl (100%) rename {templates => template}/index.json (100%) rename {templates => template}/llama2-chat.gotmpl (100%) rename {templates => template}/llama3-instruct.gotmpl (100%) rename {templates => template}/magicoder.gotmpl (100%) rename {templates => template}/mistral-instruct.gotmpl (100%) rename {templates => template}/openchat.gotmpl (100%) rename {templates => template}/phi-3.gotmpl (100%) rename {templates => template}/solar-instruct.gotmpl (100%) rename {templates => template}/starcoder2-instruct.gotmpl (100%) create mode 100644 template/template.go create mode 100644 template/template_test.go rename {templates => template}/testdata/templates.jsonl (100%) rename {templates => template}/vicuna.gotmpl (100%) rename {templates => template}/zephyr.gotmpl (100%) delete mode 100644 templates/template.go delete mode 100644 templates/template_test.go diff --git a/server/images.go b/server/images.go index 447a63a69..65ed51c76 100644 --- a/server/images.go +++ b/server/images.go @@ -28,6 +28,7 @@ import ( "github.com/ollama/ollama/format" "github.com/ollama/ollama/llm" "github.com/ollama/ollama/parser" + "github.com/ollama/ollama/template" "github.com/ollama/ollama/types/errtypes" "github.com/ollama/ollama/types/model" "github.com/ollama/ollama/version" @@ -48,12 +49,13 @@ type Model struct { ParentModel string AdapterPaths []string ProjectorPaths []string - Template string System string License []string Digest string Options map[string]interface{} Messages []Message + + Template *template.Template } func (m *Model) IsEmbedding() bool { @@ -82,10 +84,10 @@ func (m *Model) String() string { }) } - if m.Template != "" { + if m.Template != nil { modelfile.Commands = append(modelfile.Commands, parser.Command{ Name: "template", - Args: m.Template, + Args: m.Template.String(), }) } @@ -191,8 +193,7 @@ func GetModel(name string) (*Model, error) { Name: mp.GetFullTagname(), ShortName: mp.GetShortTagname(), Digest: digest, - Template: "{{ .Prompt }}", - License: []string{}, + Template: template.DefaultTemplate, } filename, err := GetBlobsPath(manifest.Config.Digest) @@ -228,13 +229,17 @@ func GetModel(name string) (*Model, error) { model.AdapterPaths = append(model.AdapterPaths, filename) case "application/vnd.ollama.image.projector": model.ProjectorPaths = append(model.ProjectorPaths, filename) - case "application/vnd.ollama.image.template": + case "application/vnd.ollama.image.prompt", + "application/vnd.ollama.image.template": bts, err := os.ReadFile(filename) if err != nil { return nil, err } - model.Template = string(bts) + model.Template, err = template.Parse(string(bts)) + if err != nil { + return nil, err + } case "application/vnd.ollama.image.system": bts, err := os.ReadFile(filename) if err != nil { @@ -242,13 +247,6 @@ func GetModel(name string) (*Model, error) { } 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": params, err := os.Open(filename) if err != nil { diff --git a/server/model.go b/server/model.go index d56e641ba..6abb5b392 100644 --- a/server/model.go +++ b/server/model.go @@ -16,7 +16,7 @@ import ( "github.com/ollama/ollama/api" "github.com/ollama/ollama/convert" "github.com/ollama/ollama/llm" - "github.com/ollama/ollama/templates" + "github.com/ollama/ollama/template" "github.com/ollama/ollama/types/model" ) @@ -258,7 +258,7 @@ func parseFromFile(ctx context.Context, file *os.File, digest string, fn func(ap func detectChatTemplate(layers []*layerGGML) ([]*layerGGML, error) { for _, layer := range layers { if s := layer.GGML.KV().ChatTemplate(); s != "" { - if t, err := templates.NamedTemplate(s); err != nil { + if t, err := template.Named(s); err != nil { slog.Debug("template detection", "error", err) } else { tmpl, err := NewLayer(t.Reader(), "application/vnd.ollama.image.template") diff --git a/server/prompt.go b/server/prompt.go index 604e69717..bfc319a50 100644 --- a/server/prompt.go +++ b/server/prompt.go @@ -4,10 +4,11 @@ import ( "fmt" "log/slog" "strings" - "text/template" + "text/template/parse" "github.com/ollama/ollama/api" + "github.com/ollama/ollama/template" ) // isResponseNode checks if the node contains .Response @@ -53,13 +54,8 @@ func formatTemplateForResponse(tmpl *template.Template, generate bool) { // Prompt renders a prompt from a template. If generate is set to true, // the response and parts of the template following it are not rendered -func Prompt(tmpl, system, prompt, response string, generate bool) (string, error) { - parsed, err := template.New("").Option("missingkey=zero").Parse(tmpl) - if err != nil { - return "", err - } - - formatTemplateForResponse(parsed, generate) +func Prompt(tmpl *template.Template, system, prompt, response string, generate bool) (string, error) { + formatTemplateForResponse(tmpl, generate) vars := map[string]any{ "System": system, @@ -68,14 +64,14 @@ func Prompt(tmpl, system, prompt, response string, generate bool) (string, error } var sb strings.Builder - if err := parsed.Execute(&sb, vars); err != nil { + if err := tmpl.Execute(&sb, vars); err != nil { return "", err } return sb.String(), nil } -func countTokens(tmpl string, system string, prompt string, response string, encode func(string) ([]int, error)) (int, error) { +func countTokens(tmpl *template.Template, system string, prompt string, response string, encode func(string) ([]int, error)) (int, error) { rendered, err := Prompt(tmpl, system, prompt, response, false) if err != nil { return 0, err @@ -91,7 +87,7 @@ func countTokens(tmpl string, system string, prompt string, response string, enc } // ChatPrompt builds up a prompt from a series of messages, truncating based on context window size -func ChatPrompt(tmpl string, messages []api.Message, window int, encode func(string) ([]int, error)) (string, error) { +func ChatPrompt(tmpl *template.Template, messages []api.Message, window int, encode func(string) ([]int, error)) (string, error) { type prompt struct { System string Prompt string diff --git a/server/prompt_test.go b/server/prompt_test.go index a7e18a70f..7df58d0bd 100644 --- a/server/prompt_test.go +++ b/server/prompt_test.go @@ -5,6 +5,7 @@ import ( "testing" "github.com/ollama/ollama/api" + "github.com/ollama/ollama/template" ) func TestPrompt(t *testing.T) { @@ -61,7 +62,12 @@ func TestPrompt(t *testing.T) { for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { - got, err := Prompt(tc.template, tc.system, tc.prompt, tc.response, tc.generate) + tmpl, err := template.Parse(tc.template) + if err != nil { + t.Fatal(err) + } + + got, err := Prompt(tmpl, tc.system, tc.prompt, tc.response, tc.generate) if err != nil { t.Errorf("error = %v", err) } @@ -192,7 +198,12 @@ func TestChatPrompt(t *testing.T) { for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { - got, err := ChatPrompt(tc.template, tc.messages, tc.window, encode) + tmpl, err := template.Parse(tc.template) + if err != nil { + t.Fatal(err) + } + + got, err := ChatPrompt(tmpl, tc.messages, tc.window, encode) if err != nil { t.Errorf("error = %v", err) } diff --git a/server/routes.go b/server/routes.go index 76ead072f..d8a4a67e7 100644 --- a/server/routes.go +++ b/server/routes.go @@ -31,6 +31,7 @@ import ( "github.com/ollama/ollama/llm" "github.com/ollama/ollama/openai" "github.com/ollama/ollama/parser" + "github.com/ollama/ollama/template" "github.com/ollama/ollama/types/errtypes" "github.com/ollama/ollama/types/model" "github.com/ollama/ollama/version" @@ -161,6 +162,12 @@ func (s *Server) GenerateHandler(c *gin.Context) { return } + tmpl, err := template.Parse(req.Template) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + checkpointLoaded := time.Now() var prompt string @@ -169,7 +176,11 @@ func (s *Server) GenerateHandler(c *gin.Context) { prompt = req.Prompt case req.Prompt != "": if req.Template == "" { - req.Template = model.Template + model.Template, err = template.Parse(req.Template) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } } if req.System == "" { @@ -187,7 +198,7 @@ func (s *Server) GenerateHandler(c *gin.Context) { sb.WriteString(req.Prompt) - p, err := Prompt(req.Template, req.System, sb.String(), "", true) + p, err := Prompt(tmpl, req.System, sb.String(), "", true) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return @@ -242,7 +253,7 @@ func (s *Server) GenerateHandler(c *gin.Context) { resp.LoadDuration = checkpointLoaded.Sub(checkpointStart) if !req.Raw { - p, err := Prompt(req.Template, req.System, req.Prompt, generated.String(), false) + p, err := Prompt(tmpl, req.System, req.Prompt, generated.String(), false) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return @@ -680,7 +691,10 @@ func GetModelInfo(req api.ShowRequest) (*api.ShowResponse, error) { } if req.Template != "" { - m.Template = req.Template + m.Template, err = template.Parse(req.Template) + if err != nil { + return nil, err + } } msgs := make([]api.Message, 0) @@ -701,7 +715,7 @@ func GetModelInfo(req api.ShowRequest) (*api.ShowResponse, error) { resp := &api.ShowResponse{ License: strings.Join(m.License, "\n"), System: m.System, - Template: m.Template, + Template: m.Template.String(), Details: modelDetails, Messages: msgs, ModifiedAt: manifest.fi.ModTime(), @@ -1246,7 +1260,7 @@ func (s *Server) ProcessHandler(c *gin.Context) { } // ChatPrompt builds up a prompt from a series of messages for the currently `loaded` model -func chatPrompt(ctx context.Context, runner *runnerRef, template string, messages []api.Message, numCtx int) (string, error) { +func chatPrompt(ctx context.Context, runner *runnerRef, template *template.Template, messages []api.Message, numCtx int) (string, error) { encode := func(s string) ([]int, error) { return runner.llama.Tokenize(ctx, s) } diff --git a/templates/alfred.gotmpl b/template/alfred.gotmpl similarity index 100% rename from templates/alfred.gotmpl rename to template/alfred.gotmpl diff --git a/templates/alpaca.gotmpl b/template/alpaca.gotmpl similarity index 100% rename from templates/alpaca.gotmpl rename to template/alpaca.gotmpl diff --git a/templates/chatml.gotmpl b/template/chatml.gotmpl similarity index 100% rename from templates/chatml.gotmpl rename to template/chatml.gotmpl diff --git a/templates/chatqa.gotmpl b/template/chatqa.gotmpl similarity index 100% rename from templates/chatqa.gotmpl rename to template/chatqa.gotmpl diff --git a/templates/codellama-70b-instruct.gotmpl b/template/codellama-70b-instruct.gotmpl similarity index 100% rename from templates/codellama-70b-instruct.gotmpl rename to template/codellama-70b-instruct.gotmpl diff --git a/templates/falcon-instruct.gotmpl b/template/falcon-instruct.gotmpl similarity index 100% rename from templates/falcon-instruct.gotmpl rename to template/falcon-instruct.gotmpl diff --git a/templates/gemma-instruct.gotmpl b/template/gemma-instruct.gotmpl similarity index 100% rename from templates/gemma-instruct.gotmpl rename to template/gemma-instruct.gotmpl diff --git a/templates/granite-instruct.gotmpl b/template/granite-instruct.gotmpl similarity index 100% rename from templates/granite-instruct.gotmpl rename to template/granite-instruct.gotmpl diff --git a/templates/index.json b/template/index.json similarity index 100% rename from templates/index.json rename to template/index.json diff --git a/templates/llama2-chat.gotmpl b/template/llama2-chat.gotmpl similarity index 100% rename from templates/llama2-chat.gotmpl rename to template/llama2-chat.gotmpl diff --git a/templates/llama3-instruct.gotmpl b/template/llama3-instruct.gotmpl similarity index 100% rename from templates/llama3-instruct.gotmpl rename to template/llama3-instruct.gotmpl diff --git a/templates/magicoder.gotmpl b/template/magicoder.gotmpl similarity index 100% rename from templates/magicoder.gotmpl rename to template/magicoder.gotmpl diff --git a/templates/mistral-instruct.gotmpl b/template/mistral-instruct.gotmpl similarity index 100% rename from templates/mistral-instruct.gotmpl rename to template/mistral-instruct.gotmpl diff --git a/templates/openchat.gotmpl b/template/openchat.gotmpl similarity index 100% rename from templates/openchat.gotmpl rename to template/openchat.gotmpl diff --git a/templates/phi-3.gotmpl b/template/phi-3.gotmpl similarity index 100% rename from templates/phi-3.gotmpl rename to template/phi-3.gotmpl diff --git a/templates/solar-instruct.gotmpl b/template/solar-instruct.gotmpl similarity index 100% rename from templates/solar-instruct.gotmpl rename to template/solar-instruct.gotmpl diff --git a/templates/starcoder2-instruct.gotmpl b/template/starcoder2-instruct.gotmpl similarity index 100% rename from templates/starcoder2-instruct.gotmpl rename to template/starcoder2-instruct.gotmpl diff --git a/template/template.go b/template/template.go new file mode 100644 index 000000000..d15f7156f --- /dev/null +++ b/template/template.go @@ -0,0 +1,158 @@ +package template + +import ( + "bytes" + "embed" + "encoding/json" + "errors" + "io" + "math" + "slices" + "strings" + "sync" + "text/template" + "text/template/parse" + + "github.com/agnivade/levenshtein" + "golang.org/x/exp/maps" +) + +//go:embed index.json +var indexBytes []byte + +//go:embed *.gotmpl +var templatesFS embed.FS + +var templatesOnce = sync.OnceValues(func() ([]*named, error) { + var templates []*named + if err := json.Unmarshal(indexBytes, &templates); err != nil { + return nil, err + } + + for _, t := range templates { + bts, err := templatesFS.ReadFile(t.Name + ".gotmpl") + if err != nil { + return nil, err + } + + // normalize line endings + t.Bytes = bytes.ReplaceAll(bts, []byte("\r\n"), []byte("\n")) + } + + return templates, nil +}) + +type named struct { + Name string `json:"name"` + Template string `json:"template"` + Bytes []byte +} + +func (t named) Reader() io.Reader { + return bytes.NewReader(t.Bytes) +} + +func Named(s string) (*named, error) { + templates, err := templatesOnce() + if err != nil { + return nil, err + } + + var template *named + score := math.MaxInt + for _, t := range templates { + if s := levenshtein.ComputeDistance(s, t.Template); s < score { + score = s + template = t + } + } + + if score < 100 { + return template, nil + } + + return nil, errors.New("no matching template found") +} + +type Template struct { + *template.Template + raw string +} + +func (t *Template) String() string { + return t.raw +} + +var DefaultTemplate, _ = Parse("{{ .Prompt }}") + +func Parse(s string) (*Template, error) { + t, err := template.New("").Option("missingkey=zero").Parse(s) + if err != nil { + return nil, err + } + + return &Template{Template: t, raw: s}, nil +} + +func (t *Template) Vars() []string { + var vars []string + for _, n := range t.Tree.Root.Nodes { + vars = append(vars, parseNode(n)...) + } + + set := make(map[string]struct{}) + for _, n := range vars { + set[strings.ToLower(n)] = struct{}{} + } + + vars = maps.Keys(set) + slices.Sort(vars) + return vars +} + +func parseNode(n parse.Node) []string { + switch n := n.(type) { + case *parse.ActionNode: + return parseNode(n.Pipe) + case *parse.IfNode: + names := parseNode(n.Pipe) + names = append(names, parseNode(n.List)...) + if n.ElseList != nil { + names = append(names, parseNode(n.ElseList)...) + } + return names + case *parse.RangeNode: + names := parseNode(n.Pipe) + names = append(names, parseNode(n.List)...) + if n.ElseList != nil { + names = append(names, parseNode(n.ElseList)...) + } + return names + case *parse.WithNode: + names := parseNode(n.Pipe) + names = append(names, parseNode(n.List)...) + if n.ElseList != nil { + names = append(names, parseNode(n.ElseList)...) + } + return names + case *parse.PipeNode: + var names []string + for _, c := range n.Cmds { + for _, a := range c.Args { + names = append(names, parseNode(a)...) + } + } + return names + case *parse.ListNode: + var names []string + for _, n := range n.Nodes { + names = append(names, parseNode(n)...) + } + + return names + case *parse.FieldNode: + return n.Ident + } + + return nil +} diff --git a/template/template_test.go b/template/template_test.go new file mode 100644 index 000000000..e5405bdb4 --- /dev/null +++ b/template/template_test.go @@ -0,0 +1,89 @@ +package template + +import ( + "bufio" + "bytes" + "encoding/json" + "io" + "os" + "path/filepath" + "slices" + "testing" + "text/template" + + "github.com/ollama/ollama/llm" +) + +func TestNamed(t *testing.T) { + f, err := os.Open(filepath.Join("testdata", "templates.jsonl")) + if err != nil { + t.Fatal(err) + } + defer f.Close() + + scanner := bufio.NewScanner(f) + for scanner.Scan() { + var ss map[string]string + if err := json.Unmarshal(scanner.Bytes(), &ss); err != nil { + t.Fatal(err) + } + + for k, v := range ss { + t.Run(k, func(t *testing.T) { + kv := llm.KV{"tokenizer.chat_template": v} + s := kv.ChatTemplate() + r, err := Named(s) + if err != nil { + t.Fatal(err) + } + + if r.Name != k { + t.Errorf("expected %q, got %q", k, r.Name) + } + + var b bytes.Buffer + if _, err := io.Copy(&b, r.Reader()); err != nil { + t.Fatal(err) + } + + tmpl, err := template.New(s).Parse(b.String()) + if err != nil { + t.Fatal(err) + } + + if tmpl.Tree.Root.String() == "" { + t.Errorf("empty %s template", k) + } + }) + } + } +} + +func TestParse(t *testing.T) { + cases := []struct { + template string + capabilities []string + }{ + {"{{ .Prompt }}", []string{"prompt"}}, + {"{{ .System }} {{ .Prompt }}", []string{"prompt", "system"}}, + {"{{ .System }} {{ .Prompt }} {{ .Response }}", []string{"prompt", "response", "system"}}, + {"{{ with .Tools }}{{ . }}{{ end }} {{ .System }} {{ .Prompt }}", []string{"prompt", "system", "tools"}}, + {"{{ range .Messages }}{{ .Role }} {{ .Content }}{{ end }}", []string{"content", "messages", "role"}}, + {"{{ range .Messages }}{{ if eq .Role \"system\" }}SYSTEM: {{ .Content }}{{ else if eq .Role \"user\" }}USER: {{ .Content }}{{ else if eq .Role \"assistant\" }}ASSISTANT: {{ .Content }}{{ end }}{{ end }}", []string{"content", "messages", "role"}}, + {"{{ .Prompt }} {{ .Suffix }}", []string{"prompt", "suffix"}}, + } + + for _, tt := range cases { + t.Run("", func(t *testing.T) { + tmpl, err := Parse(tt.template) + if err != nil { + t.Fatal(err) + } + + vars := tmpl.Vars() + if !slices.Equal(tt.capabilities, vars) { + t.Errorf("expected %v, got %v", tt.capabilities, vars) + } + }) + } +} diff --git a/templates/testdata/templates.jsonl b/template/testdata/templates.jsonl similarity index 100% rename from templates/testdata/templates.jsonl rename to template/testdata/templates.jsonl diff --git a/templates/vicuna.gotmpl b/template/vicuna.gotmpl similarity index 100% rename from templates/vicuna.gotmpl rename to template/vicuna.gotmpl diff --git a/templates/zephyr.gotmpl b/template/zephyr.gotmpl similarity index 100% rename from templates/zephyr.gotmpl rename to template/zephyr.gotmpl diff --git a/templates/template.go b/templates/template.go deleted file mode 100644 index 72bd69e9d..000000000 --- a/templates/template.go +++ /dev/null @@ -1,70 +0,0 @@ -package templates - -import ( - "bytes" - "embed" - "encoding/json" - "errors" - "io" - "math" - "sync" - - "github.com/agnivade/levenshtein" -) - -//go:embed index.json -var indexBytes []byte - -//go:embed *.gotmpl -var templatesFS embed.FS - -var templatesOnce = sync.OnceValues(func() ([]*Template, error) { - var templates []*Template - if err := json.Unmarshal(indexBytes, &templates); err != nil { - return nil, err - } - - for _, t := range templates { - bts, err := templatesFS.ReadFile(t.Name + ".gotmpl") - if err != nil { - return nil, err - } - - // normalize line endings - t.Bytes = bytes.ReplaceAll(bts, []byte("\r\n"), []byte("\n")) - } - - return templates, nil -}) - -type Template struct { - Name string `json:"name"` - Template string `json:"template"` - Bytes []byte -} - -func (t Template) Reader() io.Reader { - return bytes.NewReader(t.Bytes) -} - -func NamedTemplate(s string) (*Template, error) { - templates, err := templatesOnce() - if err != nil { - return nil, err - } - - var template *Template - score := math.MaxInt - for _, t := range templates { - if s := levenshtein.ComputeDistance(s, t.Template); s < score { - score = s - template = t - } - } - - if score < 100 { - return template, nil - } - - return nil, errors.New("no matching template found") -} diff --git a/templates/template_test.go b/templates/template_test.go deleted file mode 100644 index 61bc78374..000000000 --- a/templates/template_test.go +++ /dev/null @@ -1,59 +0,0 @@ -package templates - -import ( - "bufio" - "bytes" - "encoding/json" - "io" - "os" - "path/filepath" - "testing" - "text/template" - - "github.com/ollama/ollama/llm" -) - -func TestKVChatTemplate(t *testing.T) { - f, err := os.Open(filepath.Join("testdata", "templates.jsonl")) - if err != nil { - t.Fatal(err) - } - defer f.Close() - - scanner := bufio.NewScanner(f) - for scanner.Scan() { - var ss map[string]string - if err := json.Unmarshal(scanner.Bytes(), &ss); err != nil { - t.Fatal(err) - } - - for k, v := range ss { - t.Run(k, func(t *testing.T) { - kv := llm.KV{"tokenizer.chat_template": v} - s := kv.ChatTemplate() - r, err := NamedTemplate(s) - if err != nil { - t.Fatal(err) - } - - if r.Name != k { - t.Errorf("expected %q, got %q", k, r.Name) - } - - var b bytes.Buffer - if _, err := io.Copy(&b, r.Reader()); err != nil { - t.Fatal(err) - } - - tmpl, err := template.New(s).Parse(b.String()) - if err != nil { - t.Fatal(err) - } - - if tmpl.Tree.Root.String() == "" { - t.Errorf("empty %s template", k) - } - }) - } - } -} From a30915bde166b2f392a0ff72c61c9ac53189a962 Mon Sep 17 00:00:00 2001 From: Michael Yang Date: Tue, 11 Jun 2024 14:03:42 -0700 Subject: [PATCH 005/384] add capabilities --- server/images.go | 20 ++++++++++++++++++-- server/routes.go | 8 ++++---- template/template_test.go | 8 ++++---- 3 files changed, 26 insertions(+), 10 deletions(-) diff --git a/server/images.go b/server/images.go index 65ed51c76..5cd0a7a53 100644 --- a/server/images.go +++ b/server/images.go @@ -34,6 +34,10 @@ import ( "github.com/ollama/ollama/version" ) +type Capability string + +const CapabilityCompletion = Capability("completion") + type registryOptions struct { Insecure bool Username string @@ -58,8 +62,20 @@ type Model struct { Template *template.Template } -func (m *Model) IsEmbedding() bool { - return slices.Contains(m.Config.ModelFamilies, "bert") || slices.Contains(m.Config.ModelFamilies, "nomic-bert") +func (m *Model) Has(caps ...Capability) bool { + for _, cap := range caps { + switch cap { + case CapabilityCompletion: + if slices.Contains(m.Config.ModelFamilies, "bert") || slices.Contains(m.Config.ModelFamilies, "nomic-bert") { + return false + } + default: + slog.Error("unknown capability", "capability", cap) + return false + } + } + + return true } func (m *Model) String() string { diff --git a/server/routes.go b/server/routes.go index d8a4a67e7..8ca6dcc89 100644 --- a/server/routes.go +++ b/server/routes.go @@ -122,8 +122,8 @@ func (s *Server) GenerateHandler(c *gin.Context) { return } - if model.IsEmbedding() { - c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "embedding models do not support generate"}) + if !model.Has(CapabilityCompletion) { + c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Sprintf("%s does not support generate", req.Model)}) return } @@ -1308,8 +1308,8 @@ func (s *Server) ChatHandler(c *gin.Context) { return } - if model.IsEmbedding() { - c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "embedding models do not support chat"}) + if !model.Has(CapabilityCompletion) { + c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Sprintf("%s does not support chat", req.Model)}) return } diff --git a/template/template_test.go b/template/template_test.go index e5405bdb4..eda4634f4 100644 --- a/template/template_test.go +++ b/template/template_test.go @@ -61,8 +61,8 @@ func TestNamed(t *testing.T) { func TestParse(t *testing.T) { cases := []struct { - template string - capabilities []string + template string + vars []string }{ {"{{ .Prompt }}", []string{"prompt"}}, {"{{ .System }} {{ .Prompt }}", []string{"prompt", "system"}}, @@ -81,8 +81,8 @@ func TestParse(t *testing.T) { } vars := tmpl.Vars() - if !slices.Equal(tt.capabilities, vars) { - t.Errorf("expected %v, got %v", tt.capabilities, vars) + if !slices.Equal(tt.vars, vars) { + t.Errorf("expected %v, got %v", tt.vars, vars) } }) } From da8e2a04479f96ad9c57eaf25ed26b79b239b05c Mon Sep 17 00:00:00 2001 From: Michael Yang Date: Fri, 14 Jun 2024 14:57:49 -0700 Subject: [PATCH 006/384] use kvs to detect embedding models --- server/images.go | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/server/images.go b/server/images.go index 5cd0a7a53..a62991f16 100644 --- a/server/images.go +++ b/server/images.go @@ -66,7 +66,21 @@ func (m *Model) Has(caps ...Capability) bool { for _, cap := range caps { switch cap { case CapabilityCompletion: - if slices.Contains(m.Config.ModelFamilies, "bert") || slices.Contains(m.Config.ModelFamilies, "nomic-bert") { + f, err := os.Open(m.ModelPath) + if err != nil { + slog.Error("couldn't open model file", "error", err) + continue + } + defer f.Close() + + // TODO(mxyng): decode the GGML into model to avoid doing this multiple times + ggml, _, err := llm.DecodeGGML(f, 0) + if err != nil { + slog.Error("couldn't decode ggml", "error", err) + continue + } + + if _, ok := ggml.KV()[fmt.Sprintf("%s.pooling_type", ggml.KV().Architecture())]; ok { return false } default: From 88bcd79bb9a4b2baa739efe2ccabcbcf3c89bdb5 Mon Sep 17 00:00:00 2001 From: Michael Yang Date: Sun, 30 Jun 2024 11:10:40 -0700 Subject: [PATCH 007/384] err on insecure path --- server/model.go | 8 +++----- server/model_test.go | 24 ++++++++++++++++++++++-- 2 files changed, 25 insertions(+), 7 deletions(-) diff --git a/server/model.go b/server/model.go index d56e641ba..7d5957a18 100644 --- a/server/model.go +++ b/server/model.go @@ -11,7 +11,6 @@ import ( "net/http" "os" "path/filepath" - "strings" "github.com/ollama/ollama/api" "github.com/ollama/ollama/convert" @@ -91,12 +90,11 @@ func extractFromZipFile(p string, file *os.File, fn func(api.ProgressResponse)) fn(api.ProgressResponse{Status: "unpacking model metadata"}) for _, f := range r.File { - n := filepath.Join(p, f.Name) - if !strings.HasPrefix(n, p) { - slog.Warn("skipped extracting file outside of context", "name", f.Name) - continue + if !filepath.IsLocal(f.Name) { + return fmt.Errorf("%w: %s", zip.ErrInsecurePath, f.Name) } + n := filepath.Join(p, f.Name) if err := os.MkdirAll(filepath.Dir(n), 0o750); err != nil { return err } diff --git a/server/model_test.go b/server/model_test.go index c3023eb2b..a383b7e72 100644 --- a/server/model_test.go +++ b/server/model_test.go @@ -3,10 +3,12 @@ package server import ( "archive/zip" "bytes" + "errors" "io" "os" "path/filepath" "slices" + "strings" "testing" "github.com/ollama/ollama/api" @@ -39,13 +41,31 @@ func TestExtractFromZipFile(t *testing.T) { cases := []struct { name string expect []string + err error }{ { name: "good", expect: []string{"good"}, }, { - name: filepath.Join("..", "..", "..", "..", "..", "..", "..", "..", "..", "..", "..", "..", "..", "..", "..", "..", "bad"), + name: strings.Join([]string{"path", "..", "to", "good"}, string(os.PathSeparator)), + expect: []string{filepath.Join("to", "good")}, + }, + { + name: strings.Join([]string{"path", "..", "to", "..", "good"}, string(os.PathSeparator)), + expect: []string{"good"}, + }, + { + name: strings.Join([]string{"path", "to", "..", "..", "good"}, string(os.PathSeparator)), + expect: []string{"good"}, + }, + { + name: strings.Join([]string{"..", "..", "..", "..", "..", "..", "..", "..", "..", "..", "..", "..", "..", "..", "..", "..", "bad"}, string(os.PathSeparator)), + err: zip.ErrInsecurePath, + }, + { + name: strings.Join([]string{"path", "..", "..", "to", "bad"}, string(os.PathSeparator)), + err: zip.ErrInsecurePath, }, } @@ -55,7 +75,7 @@ func TestExtractFromZipFile(t *testing.T) { defer f.Close() tempDir := t.TempDir() - if err := extractFromZipFile(tempDir, f, func(api.ProgressResponse) {}); err != nil { + if err := extractFromZipFile(tempDir, f, func(api.ProgressResponse) {}); !errors.Is(err, tt.err) { t.Fatal(err) } From 4f67b39d262b1997aa96c47585f1d8e8443d0f90 Mon Sep 17 00:00:00 2001 From: Daniel Hiltgen Date: Tue, 2 Jul 2024 09:22:17 -0700 Subject: [PATCH 008/384] Centos 7 EOL broke mirrors As of July 1st 2024: Could not resolve host: mirrorlist.centos.org This is expected due to EOL dates. --- scripts/rh_linux_deps.sh | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/scripts/rh_linux_deps.sh b/scripts/rh_linux_deps.sh index ed60e4304..81648d68e 100644 --- a/scripts/rh_linux_deps.sh +++ b/scripts/rh_linux_deps.sh @@ -6,10 +6,21 @@ set -ex MACHINE=$(uname -m) if grep -i "centos" /etc/system-release >/dev/null; then + # As of 7/1/2024 mirrorlist.centos.org has been taken offline, so adjust accordingly + sed -i s/mirror.centos.org/vault.centos.org/g /etc/yum.repos.d/*.repo + sed -i s/^#.*baseurl=http/baseurl=http/g /etc/yum.repos.d/*.repo + sed -i s/^mirrorlist=http/#mirrorlist=http/g /etc/yum.repos.d/*.repo + # Centos 7 derivatives have too old of a git version to run our generate script # uninstall and ignore failures yum remove -y git yum -y install epel-release centos-release-scl + + # The release packages reinstate the mirrors, undo that again + sed -i s/mirror.centos.org/vault.centos.org/g /etc/yum.repos.d/*.repo + sed -i s/^#.*baseurl=http/baseurl=http/g /etc/yum.repos.d/*.repo + sed -i s/^mirrorlist=http/#mirrorlist=http/g /etc/yum.repos.d/*.repo + yum -y install dnf if [ "${MACHINE}" = "x86_64" ]; then yum -y install https://repo.ius.io/ius-release-el7.rpm From 020bd60ab2f156661b072515cd2c27d59b956535 Mon Sep 17 00:00:00 2001 From: Daniel Hiltgen Date: Tue, 2 Jul 2024 10:23:05 -0700 Subject: [PATCH 009/384] Switch amd container image base to rocky 8 The centos 7 arm mirrors have disappeared due to the EOL 2 days ago, and the vault sed workaround which works for x86 doesn't work for arm. --- Dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index 98a3ddfd2..b2c5c4a2f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -70,12 +70,12 @@ RUN OLLAMA_SKIP_STATIC_GENERATE=1 OLLAMA_CPU_TARGET="cpu_avx" sh gen_linux.sh FROM --platform=linux/amd64 cpu-builder-amd64 AS cpu_avx2-build-amd64 RUN OLLAMA_SKIP_STATIC_GENERATE=1 OLLAMA_CPU_TARGET="cpu_avx2" sh gen_linux.sh -FROM --platform=linux/arm64 centos:7 AS cpu-builder-arm64 +FROM --platform=linux/arm64 rockylinux:8 AS cpu-builder-arm64 ARG CMAKE_VERSION ARG GOLANG_VERSION COPY ./scripts/rh_linux_deps.sh / RUN CMAKE_VERSION=${CMAKE_VERSION} GOLANG_VERSION=${GOLANG_VERSION} sh /rh_linux_deps.sh -ENV PATH /opt/rh/devtoolset-10/root/usr/bin:$PATH +ENV PATH /opt/rh/gcc-toolset-10/root/usr/bin:$PATH COPY --from=llm-code / /go/src/github.com/ollama/ollama/ ARG OLLAMA_CUSTOM_CPU_DEFS ARG CGO_CFLAGS From 996bb1b85e0c1b3ae64246a50ea412dc2a2e30d8 Mon Sep 17 00:00:00 2001 From: royjhan <65097070+royjhan@users.noreply.github.com> Date: Tue, 2 Jul 2024 11:50:56 -0700 Subject: [PATCH 010/384] OpenAI: /v1/models and /v1/models/{model} compatibility (#5007) * OpenAI v1 models * Refactor Writers * Add Test Co-Authored-By: Attila Kerekes * Credit Co-Author Co-Authored-By: Attila Kerekes <439392+keriati@users.noreply.github.com> * Empty List Testing * Use Namespace for Ownedby * Update Test * Add back envconfig * v1/models docs * Use ModelName Parser * Test Names * Remove Docs * Clean Up * Test name Co-authored-by: Jeffrey Morgan * Add Middleware for Chat and List * Testing Cleanup * Test with Fatal * Add functionality to chat test * OpenAI: /v1/models/{model} compatibility (#5028) * Retrieve Model * OpenAI Delete Model * Retrieve Middleware * Remove Delete from Branch * Update Test * Middleware Test File * Function name * Cleanup * Test Update * Test Update --------- Co-authored-by: Attila Kerekes <439392+keriati@users.noreply.github.com> Co-authored-by: Jeffrey Morgan --- api/types.go | 7 ++ docs/openai.md | 1 + openai/openai.go | 163 ++++++++++++++++++++++++++++++++++++---- openai/openai_test.go | 170 ++++++++++++++++++++++++++++++++++++++++++ server/routes.go | 4 +- server/routes_test.go | 56 ++++++++++++++ 6 files changed, 387 insertions(+), 14 deletions(-) create mode 100644 openai/openai_test.go diff --git a/api/types.go b/api/types.go index 95ed5d37e..428281ba6 100644 --- a/api/types.go +++ b/api/types.go @@ -345,6 +345,13 @@ type ProcessModelResponse struct { SizeVRAM int64 `json:"size_vram"` } +type RetrieveModelResponse struct { + Id string `json:"id"` + Object string `json:"object"` + Created int64 `json:"created"` + OwnedBy string `json:"owned_by"` +} + type TokenResponse struct { Token string `json:"token"` } diff --git a/docs/openai.md b/docs/openai.md index 81b967eb7..9dda05c3a 100644 --- a/docs/openai.md +++ b/docs/openai.md @@ -65,6 +65,7 @@ curl http://localhost:11434/v1/chat/completions \ } ] }' + ``` ## Endpoints diff --git a/openai/openai.go b/openai/openai.go index 706d31aa2..01da44409 100644 --- a/openai/openai.go +++ b/openai/openai.go @@ -12,6 +12,7 @@ import ( "github.com/gin-gonic/gin" "github.com/ollama/ollama/api" + "github.com/ollama/ollama/types/model" ) type Error struct { @@ -85,6 +86,18 @@ type ChatCompletionChunk struct { Choices []ChunkChoice `json:"choices"` } +type Model struct { + Id string `json:"id"` + Object string `json:"object"` + Created int64 `json:"created"` + OwnedBy string `json:"owned_by"` +} + +type ListCompletion struct { + Object string `json:"object"` + Data []Model `json:"data"` +} + func NewError(code int, message string) ErrorResponse { var etype string switch code { @@ -145,7 +158,33 @@ func toChunk(id string, r api.ChatResponse) ChatCompletionChunk { } } -func fromRequest(r ChatCompletionRequest) api.ChatRequest { +func toListCompletion(r api.ListResponse) ListCompletion { + var data []Model + for _, m := range r.Models { + data = append(data, Model{ + Id: m.Name, + Object: "model", + Created: m.ModifiedAt.Unix(), + OwnedBy: model.ParseName(m.Name).Namespace, + }) + } + + return ListCompletion{ + Object: "list", + Data: data, + } +} + +func toModel(r api.ShowResponse, m string) Model { + return Model{ + Id: m, + Object: "model", + Created: r.ModifiedAt.Unix(), + OwnedBy: model.ParseName(m).Namespace, + } +} + +func fromChatRequest(r ChatCompletionRequest) api.ChatRequest { var messages []api.Message for _, msg := range r.Messages { messages = append(messages, api.Message{Role: msg.Role, Content: msg.Content}) @@ -208,13 +247,26 @@ func fromRequest(r ChatCompletionRequest) api.ChatRequest { } } -type writer struct { - stream bool - id string +type BaseWriter struct { gin.ResponseWriter } -func (w *writer) writeError(code int, data []byte) (int, error) { +type ChatWriter struct { + stream bool + id string + BaseWriter +} + +type ListWriter struct { + BaseWriter +} + +type RetrieveWriter struct { + BaseWriter + model string +} + +func (w *BaseWriter) writeError(code int, data []byte) (int, error) { var serr api.StatusError err := json.Unmarshal(data, &serr) if err != nil { @@ -230,7 +282,7 @@ func (w *writer) writeError(code int, data []byte) (int, error) { return len(data), nil } -func (w *writer) writeResponse(data []byte) (int, error) { +func (w *ChatWriter) writeResponse(data []byte) (int, error) { var chatResponse api.ChatResponse err := json.Unmarshal(data, &chatResponse) if err != nil { @@ -270,7 +322,7 @@ func (w *writer) writeResponse(data []byte) (int, error) { return len(data), nil } -func (w *writer) Write(data []byte) (int, error) { +func (w *ChatWriter) Write(data []byte) (int, error) { code := w.ResponseWriter.Status() if code != http.StatusOK { return w.writeError(code, data) @@ -279,7 +331,92 @@ func (w *writer) Write(data []byte) (int, error) { return w.writeResponse(data) } -func Middleware() gin.HandlerFunc { +func (w *ListWriter) writeResponse(data []byte) (int, error) { + var listResponse api.ListResponse + err := json.Unmarshal(data, &listResponse) + if err != nil { + return 0, err + } + + w.ResponseWriter.Header().Set("Content-Type", "application/json") + err = json.NewEncoder(w.ResponseWriter).Encode(toListCompletion(listResponse)) + if err != nil { + return 0, err + } + + return len(data), nil +} + +func (w *ListWriter) Write(data []byte) (int, error) { + code := w.ResponseWriter.Status() + if code != http.StatusOK { + return w.writeError(code, data) + } + + return w.writeResponse(data) +} + +func (w *RetrieveWriter) writeResponse(data []byte) (int, error) { + var showResponse api.ShowResponse + err := json.Unmarshal(data, &showResponse) + if err != nil { + return 0, err + } + + // retrieve completion + w.ResponseWriter.Header().Set("Content-Type", "application/json") + err = json.NewEncoder(w.ResponseWriter).Encode(toModel(showResponse, w.model)) + if err != nil { + return 0, err + } + + return len(data), nil +} + +func (w *RetrieveWriter) Write(data []byte) (int, error) { + code := w.ResponseWriter.Status() + if code != http.StatusOK { + return w.writeError(code, data) + } + + return w.writeResponse(data) +} + +func ListMiddleware() gin.HandlerFunc { + return func(c *gin.Context) { + w := &ListWriter{ + BaseWriter: BaseWriter{ResponseWriter: c.Writer}, + } + + c.Writer = w + + c.Next() + } +} + +func RetrieveMiddleware() gin.HandlerFunc { + return func(c *gin.Context) { + var b bytes.Buffer + if err := json.NewEncoder(&b).Encode(api.ShowRequest{Name: c.Param("model")}); err != nil { + c.AbortWithStatusJSON(http.StatusInternalServerError, NewError(http.StatusInternalServerError, err.Error())) + return + } + + c.Request.Body = io.NopCloser(&b) + + // response writer + w := &RetrieveWriter{ + BaseWriter: BaseWriter{ResponseWriter: c.Writer}, + model: c.Param("model"), + } + + c.Writer = w + + c.Next() + } +} + +func ChatMiddleware() gin.HandlerFunc { return func(c *gin.Context) { var req ChatCompletionRequest err := c.ShouldBindJSON(&req) @@ -294,17 +431,17 @@ func Middleware() gin.HandlerFunc { } var b bytes.Buffer - if err := json.NewEncoder(&b).Encode(fromRequest(req)); err != nil { + if err := json.NewEncoder(&b).Encode(fromChatRequest(req)); err != nil { c.AbortWithStatusJSON(http.StatusInternalServerError, NewError(http.StatusInternalServerError, err.Error())) return } c.Request.Body = io.NopCloser(&b) - w := &writer{ - ResponseWriter: c.Writer, - stream: req.Stream, - id: fmt.Sprintf("chatcmpl-%d", rand.Intn(999)), + w := &ChatWriter{ + BaseWriter: BaseWriter{ResponseWriter: c.Writer}, + stream: req.Stream, + id: fmt.Sprintf("chatcmpl-%d", rand.Intn(999)), } c.Writer = w diff --git a/openai/openai_test.go b/openai/openai_test.go new file mode 100644 index 000000000..1f335b965 --- /dev/null +++ b/openai/openai_test.go @@ -0,0 +1,170 @@ +package openai + +import ( + "bytes" + "encoding/json" + "io" + "net/http" + "net/http/httptest" + "testing" + "time" + + "github.com/gin-gonic/gin" + "github.com/ollama/ollama/api" + "github.com/stretchr/testify/assert" +) + +func TestMiddleware(t *testing.T) { + type testCase struct { + Name string + Method string + Path string + TestPath string + Handler func() gin.HandlerFunc + Endpoint func(c *gin.Context) + Setup func(t *testing.T, req *http.Request) + Expected func(t *testing.T, resp *httptest.ResponseRecorder) + } + + testCases := []testCase{ + { + Name: "chat handler", + Method: http.MethodPost, + Path: "/api/chat", + TestPath: "/api/chat", + Handler: ChatMiddleware, + Endpoint: func(c *gin.Context) { + var chatReq api.ChatRequest + if err := c.ShouldBindJSON(&chatReq); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "invalid request"}) + return + } + + userMessage := chatReq.Messages[0].Content + var assistantMessage string + + switch userMessage { + case "Hello": + assistantMessage = "Hello!" + default: + assistantMessage = "I'm not sure how to respond to that." + } + + c.JSON(http.StatusOK, api.ChatResponse{ + Message: api.Message{ + Role: "assistant", + Content: assistantMessage, + }, + }) + }, + Setup: func(t *testing.T, req *http.Request) { + body := ChatCompletionRequest{ + Model: "test-model", + Messages: []Message{{Role: "user", Content: "Hello"}}, + } + + bodyBytes, _ := json.Marshal(body) + + req.Body = io.NopCloser(bytes.NewReader(bodyBytes)) + req.Header.Set("Content-Type", "application/json") + }, + Expected: func(t *testing.T, resp *httptest.ResponseRecorder) { + var chatResp ChatCompletion + if err := json.NewDecoder(resp.Body).Decode(&chatResp); err != nil { + t.Fatal(err) + } + + if chatResp.Object != "chat.completion" { + t.Fatalf("expected chat.completion, got %s", chatResp.Object) + } + + if chatResp.Choices[0].Message.Content != "Hello!" { + t.Fatalf("expected Hello!, got %s", chatResp.Choices[0].Message.Content) + } + }, + }, + { + Name: "list handler", + Method: http.MethodGet, + Path: "/api/tags", + TestPath: "/api/tags", + Handler: ListMiddleware, + Endpoint: func(c *gin.Context) { + c.JSON(http.StatusOK, api.ListResponse{ + Models: []api.ListModelResponse{ + { + Name: "Test Model", + }, + }, + }) + }, + Expected: func(t *testing.T, resp *httptest.ResponseRecorder) { + var listResp ListCompletion + if err := json.NewDecoder(resp.Body).Decode(&listResp); err != nil { + t.Fatal(err) + } + + if listResp.Object != "list" { + t.Fatalf("expected list, got %s", listResp.Object) + } + + if len(listResp.Data) != 1 { + t.Fatalf("expected 1, got %d", len(listResp.Data)) + } + + if listResp.Data[0].Id != "Test Model" { + t.Fatalf("expected Test Model, got %s", listResp.Data[0].Id) + } + }, + }, + { + Name: "retrieve model", + Method: http.MethodGet, + Path: "/api/show/:model", + TestPath: "/api/show/test-model", + Handler: RetrieveMiddleware, + Endpoint: func(c *gin.Context) { + c.JSON(http.StatusOK, api.ShowResponse{ + ModifiedAt: time.Date(2024, 6, 17, 13, 45, 0, 0, time.UTC), + }) + }, + Expected: func(t *testing.T, resp *httptest.ResponseRecorder) { + var retrieveResp Model + if err := json.NewDecoder(resp.Body).Decode(&retrieveResp); err != nil { + t.Fatal(err) + } + + if retrieveResp.Object != "model" { + t.Fatalf("Expected object to be model, got %s", retrieveResp.Object) + } + + if retrieveResp.Id != "test-model" { + t.Fatalf("Expected id to be test-model, got %s", retrieveResp.Id) + } + }, + }, + } + + gin.SetMode(gin.TestMode) + router := gin.New() + + for _, tc := range testCases { + t.Run(tc.Name, func(t *testing.T) { + router = gin.New() + router.Use(tc.Handler()) + router.Handle(tc.Method, tc.Path, tc.Endpoint) + req, _ := http.NewRequest(tc.Method, tc.TestPath, nil) + + if tc.Setup != nil { + tc.Setup(t, req) + } + + resp := httptest.NewRecorder() + router.ServeHTTP(resp, req) + + assert.Equal(t, http.StatusOK, resp.Code) + + tc.Expected(t, resp) + }) + } +} diff --git a/server/routes.go b/server/routes.go index 76ead072f..ad2364507 100644 --- a/server/routes.go +++ b/server/routes.go @@ -1039,7 +1039,9 @@ func (s *Server) GenerateRoutes() http.Handler { r.GET("/api/ps", s.ProcessHandler) // Compatibility endpoints - r.POST("/v1/chat/completions", openai.Middleware(), s.ChatHandler) + r.POST("/v1/chat/completions", openai.ChatMiddleware(), s.ChatHandler) + r.GET("/v1/models", openai.ListMiddleware(), s.ListModelsHandler) + r.GET("/v1/models/:model", openai.RetrieveMiddleware(), s.ShowModelHandler) for _, method := range []string{http.MethodGet, http.MethodHead} { r.Handle(method, "/", func(c *gin.Context) { diff --git a/server/routes_test.go b/server/routes_test.go index 5a5c0fbba..50eaf7e97 100644 --- a/server/routes_test.go +++ b/server/routes_test.go @@ -20,6 +20,7 @@ import ( "github.com/ollama/ollama/api" "github.com/ollama/ollama/envconfig" "github.com/ollama/ollama/llm" + "github.com/ollama/ollama/openai" "github.com/ollama/ollama/parser" "github.com/ollama/ollama/types/model" "github.com/ollama/ollama/version" @@ -105,6 +106,24 @@ func Test_Routes(t *testing.T) { assert.Empty(t, len(modelList.Models)) }, }, + { + Name: "openai empty list", + Method: http.MethodGet, + Path: "/v1/models", + Expected: func(t *testing.T, resp *http.Response) { + contentType := resp.Header.Get("Content-Type") + assert.Equal(t, "application/json", contentType) + body, err := io.ReadAll(resp.Body) + require.NoError(t, err) + + var modelList openai.ListCompletion + err = json.Unmarshal(body, &modelList) + require.NoError(t, err) + + assert.Equal(t, "list", modelList.Object) + assert.Empty(t, modelList.Data) + }, + }, { Name: "Tags Handler (yes tags)", Method: http.MethodGet, @@ -128,6 +147,25 @@ func Test_Routes(t *testing.T) { assert.Equal(t, "test-model:latest", modelList.Models[0].Name) }, }, + { + Name: "openai list models with tags", + Method: http.MethodGet, + Path: "/v1/models", + Expected: func(t *testing.T, resp *http.Response) { + contentType := resp.Header.Get("Content-Type") + assert.Equal(t, "application/json", contentType) + body, err := io.ReadAll(resp.Body) + require.NoError(t, err) + + var modelList openai.ListCompletion + err = json.Unmarshal(body, &modelList) + require.NoError(t, err) + + assert.Len(t, modelList.Data, 1) + assert.Equal(t, "test-model:latest", modelList.Data[0].Id) + assert.Equal(t, "library", modelList.Data[0].OwnedBy) + }, + }, { Name: "Create Model Handler", Method: http.MethodPost, @@ -216,6 +254,24 @@ func Test_Routes(t *testing.T) { assert.InDelta(t, 0, showResp.ModelInfo["general.parameter_count"], 1e-9, "Parameter count should be 0") }, }, + { + Name: "openai retrieve model handler", + Method: http.MethodGet, + Path: "/v1/models/show-model", + Expected: func(t *testing.T, resp *http.Response) { + contentType := resp.Header.Get("Content-Type") + assert.Equal(t, "application/json", contentType) + body, err := io.ReadAll(resp.Body) + require.NoError(t, err) + + var retrieveResp api.RetrieveModelResponse + err = json.Unmarshal(body, &retrieveResp) + require.NoError(t, err) + + assert.Equal(t, "show-model", retrieveResp.Id) + assert.Equal(t, "library", retrieveResp.OwnedBy) + }, + }, } t.Setenv("OLLAMA_MODELS", t.TempDir()) From 69c04eecc4b969149e43d6941f06a7d60dc5d191 Mon Sep 17 00:00:00 2001 From: Daniel Hiltgen Date: Tue, 2 Jul 2024 12:46:14 -0700 Subject: [PATCH 011/384] Add windows radeon concurreny note --- docs/faq.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/faq.md b/docs/faq.md index 841f1d13d..574112461 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -266,8 +266,10 @@ If there is insufficient available memory to load a new model request while one Parallel request processing for a given model results in increasing the context size by the number of parallel requests. For example, a 2K context with 4 parallel requests will result in an 8K context and additional memory allocation. -The following server settings may be used to adjust how Ollama handles concurrent requests: +The following server settings may be used to adjust how Ollama handles concurrent requests on most platforms: - `OLLAMA_MAX_LOADED_MODELS` - The maximum number of models that can be loaded concurrently provided they fit in available memory. The default is 3 * the number of GPUs or 3 for CPU inference. - `OLLAMA_NUM_PARALLEL` - The maximum number of parallel requests each model will process at the same time. The default will auto-select either 4 or 1 based on available memory. - `OLLAMA_MAX_QUEUE` - The maximum number of requests Ollama will queue when busy before rejecting additional requests. The default is 512 + +Note: Windows with Radeon GPUs currently default to 1 model maximum due to limitations in ROCm v5.7 for available VRAM reporting. Once ROCm v6 is available, Windows Radeon will follow the defaults above. You may enable concurrent model loads on Radeon on Windows, but ensure you don't load more models than will fit into your GPUs VRAM. \ No newline at end of file From d626b99b547c43e57390cec90ba2ae01adf0f429 Mon Sep 17 00:00:00 2001 From: royjhan <65097070+royjhan@users.noreply.github.com> Date: Tue, 2 Jul 2024 16:01:45 -0700 Subject: [PATCH 012/384] OpenAI: v1/completions compatibility (#5209) * OpenAI v1 models * Refactor Writers * Add Test Co-Authored-By: Attila Kerekes * Credit Co-Author Co-Authored-By: Attila Kerekes <439392+keriati@users.noreply.github.com> * Empty List Testing * Use Namespace for Ownedby * Update Test * Add back envconfig * v1/models docs * Use ModelName Parser * Test Names * Remove Docs * Clean Up * Test name Co-authored-by: Jeffrey Morgan * Add Middleware for Chat and List * Completions Endpoint * Testing Cleanup * Test with Fatal * Add functionality to chat test * Rename function * float types * type cleanup * cleaning * more cleaning * Extra test cases * merge conflicts * merge conflicts * merge conflicts * merge conflicts * cleaning * cleaning --------- Co-authored-by: Attila Kerekes <439392+keriati@users.noreply.github.com> Co-authored-by: Jeffrey Morgan --- openai/openai.go | 223 +++++++++++++++++++++++++++++++++++++++++- openai/openai_test.go | 132 ++++++++++++++++++++++++- server/routes.go | 1 + 3 files changed, 353 insertions(+), 3 deletions(-) diff --git a/openai/openai.go b/openai/openai.go index 01da44409..f1e75bf21 100644 --- a/openai/openai.go +++ b/openai/openai.go @@ -43,6 +43,12 @@ type ChunkChoice struct { FinishReason *string `json:"finish_reason"` } +type CompleteChunkChoice struct { + Text string `json:"text"` + Index int `json:"index"` + FinishReason *string `json:"finish_reason"` +} + type Usage struct { PromptTokens int `json:"prompt_tokens"` CompletionTokens int `json:"completion_tokens"` @@ -86,6 +92,39 @@ type ChatCompletionChunk struct { Choices []ChunkChoice `json:"choices"` } +// TODO (https://github.com/ollama/ollama/issues/5259): support []string, []int and [][]int +type CompletionRequest struct { + Model string `json:"model"` + Prompt string `json:"prompt"` + FrequencyPenalty float32 `json:"frequency_penalty"` + MaxTokens *int `json:"max_tokens"` + PresencePenalty float32 `json:"presence_penalty"` + Seed *int `json:"seed"` + Stop any `json:"stop"` + Stream bool `json:"stream"` + Temperature *float32 `json:"temperature"` + TopP float32 `json:"top_p"` +} + +type Completion struct { + Id string `json:"id"` + Object string `json:"object"` + Created int64 `json:"created"` + Model string `json:"model"` + SystemFingerprint string `json:"system_fingerprint"` + Choices []CompleteChunkChoice `json:"choices"` + Usage Usage `json:"usage,omitempty"` +} + +type CompletionChunk struct { + Id string `json:"id"` + Object string `json:"object"` + Created int64 `json:"created"` + Choices []CompleteChunkChoice `json:"choices"` + Model string `json:"model"` + SystemFingerprint string `json:"system_fingerprint"` +} + type Model struct { Id string `json:"id"` Object string `json:"object"` @@ -158,6 +197,52 @@ func toChunk(id string, r api.ChatResponse) ChatCompletionChunk { } } +func toCompletion(id string, r api.GenerateResponse) Completion { + return Completion{ + Id: id, + Object: "text_completion", + Created: r.CreatedAt.Unix(), + Model: r.Model, + SystemFingerprint: "fp_ollama", + Choices: []CompleteChunkChoice{{ + Text: r.Response, + Index: 0, + FinishReason: func(reason string) *string { + if len(reason) > 0 { + return &reason + } + return nil + }(r.DoneReason), + }}, + Usage: Usage{ + // TODO: ollama returns 0 for prompt eval if the prompt was cached, but openai returns the actual count + PromptTokens: r.PromptEvalCount, + CompletionTokens: r.EvalCount, + TotalTokens: r.PromptEvalCount + r.EvalCount, + }, + } +} + +func toCompleteChunk(id string, r api.GenerateResponse) CompletionChunk { + return CompletionChunk{ + Id: id, + Object: "text_completion", + Created: time.Now().Unix(), + Model: r.Model, + SystemFingerprint: "fp_ollama", + Choices: []CompleteChunkChoice{{ + Text: r.Response, + Index: 0, + FinishReason: func(reason string) *string { + if len(reason) > 0 { + return &reason + } + return nil + }(r.DoneReason), + }}, + } +} + func toListCompletion(r api.ListResponse) ListCompletion { var data []Model for _, m := range r.Models { @@ -195,7 +280,7 @@ func fromChatRequest(r ChatCompletionRequest) api.ChatRequest { switch stop := r.Stop.(type) { case string: options["stop"] = []string{stop} - case []interface{}: + case []any: var stops []string for _, s := range stop { if str, ok := s.(string); ok { @@ -247,6 +332,52 @@ func fromChatRequest(r ChatCompletionRequest) api.ChatRequest { } } +func fromCompleteRequest(r CompletionRequest) (api.GenerateRequest, error) { + options := make(map[string]any) + + switch stop := r.Stop.(type) { + case string: + options["stop"] = []string{stop} + case []string: + options["stop"] = stop + default: + if r.Stop != nil { + return api.GenerateRequest{}, fmt.Errorf("invalid type for 'stop' field: %T", r.Stop) + } + } + + if r.MaxTokens != nil { + options["num_predict"] = *r.MaxTokens + } + + if r.Temperature != nil { + options["temperature"] = *r.Temperature * 2.0 + } else { + options["temperature"] = 1.0 + } + + if r.Seed != nil { + options["seed"] = *r.Seed + } + + options["frequency_penalty"] = r.FrequencyPenalty * 2.0 + + options["presence_penalty"] = r.PresencePenalty * 2.0 + + if r.TopP != 0.0 { + options["top_p"] = r.TopP + } else { + options["top_p"] = 1.0 + } + + return api.GenerateRequest{ + Model: r.Model, + Prompt: r.Prompt, + Options: options, + Stream: &r.Stream, + }, nil +} + type BaseWriter struct { gin.ResponseWriter } @@ -257,6 +388,12 @@ type ChatWriter struct { BaseWriter } +type CompleteWriter struct { + stream bool + id string + BaseWriter +} + type ListWriter struct { BaseWriter } @@ -331,6 +468,55 @@ func (w *ChatWriter) Write(data []byte) (int, error) { return w.writeResponse(data) } +func (w *CompleteWriter) writeResponse(data []byte) (int, error) { + var generateResponse api.GenerateResponse + err := json.Unmarshal(data, &generateResponse) + if err != nil { + return 0, err + } + + // completion chunk + if w.stream { + d, err := json.Marshal(toCompleteChunk(w.id, generateResponse)) + if err != nil { + return 0, err + } + + w.ResponseWriter.Header().Set("Content-Type", "text/event-stream") + _, err = w.ResponseWriter.Write([]byte(fmt.Sprintf("data: %s\n\n", d))) + if err != nil { + return 0, err + } + + if generateResponse.Done { + _, err = w.ResponseWriter.Write([]byte("data: [DONE]\n\n")) + if err != nil { + return 0, err + } + } + + return len(data), nil + } + + // completion + w.ResponseWriter.Header().Set("Content-Type", "application/json") + err = json.NewEncoder(w.ResponseWriter).Encode(toCompletion(w.id, generateResponse)) + if err != nil { + return 0, err + } + + return len(data), nil +} + +func (w *CompleteWriter) Write(data []byte) (int, error) { + code := w.ResponseWriter.Status() + if code != http.StatusOK { + return w.writeError(code, data) + } + + return w.writeResponse(data) +} + func (w *ListWriter) writeResponse(data []byte) (int, error) { var listResponse api.ListResponse err := json.Unmarshal(data, &listResponse) @@ -416,6 +602,41 @@ func RetrieveMiddleware() gin.HandlerFunc { } } +func CompletionsMiddleware() gin.HandlerFunc { + return func(c *gin.Context) { + var req CompletionRequest + err := c.ShouldBindJSON(&req) + if err != nil { + c.AbortWithStatusJSON(http.StatusBadRequest, NewError(http.StatusBadRequest, err.Error())) + return + } + + var b bytes.Buffer + genReq, err := fromCompleteRequest(req) + if err != nil { + c.AbortWithStatusJSON(http.StatusBadRequest, NewError(http.StatusBadRequest, err.Error())) + return + } + + if err := json.NewEncoder(&b).Encode(genReq); err != nil { + c.AbortWithStatusJSON(http.StatusInternalServerError, NewError(http.StatusInternalServerError, err.Error())) + return + } + + c.Request.Body = io.NopCloser(&b) + + w := &CompleteWriter{ + BaseWriter: BaseWriter{ResponseWriter: c.Writer}, + stream: req.Stream, + id: fmt.Sprintf("cmpl-%d", rand.Intn(999)), + } + + c.Writer = w + + c.Next() + } +} + func ChatMiddleware() gin.HandlerFunc { return func(c *gin.Context) { var req ChatCompletionRequest diff --git a/openai/openai_test.go b/openai/openai_test.go index 1f335b965..4d21382c6 100644 --- a/openai/openai_test.go +++ b/openai/openai_test.go @@ -3,9 +3,11 @@ package openai import ( "bytes" "encoding/json" + "fmt" "io" "net/http" "net/http/httptest" + "strings" "testing" "time" @@ -69,6 +71,8 @@ func TestMiddleware(t *testing.T) { req.Header.Set("Content-Type", "application/json") }, Expected: func(t *testing.T, resp *httptest.ResponseRecorder) { + assert.Equal(t, http.StatusOK, resp.Code) + var chatResp ChatCompletion if err := json.NewDecoder(resp.Body).Decode(&chatResp); err != nil { t.Fatal(err) @@ -83,6 +87,130 @@ func TestMiddleware(t *testing.T) { } }, }, + { + Name: "completions handler", + Method: http.MethodPost, + Path: "/api/generate", + TestPath: "/api/generate", + Handler: CompletionsMiddleware, + Endpoint: func(c *gin.Context) { + c.JSON(http.StatusOK, api.GenerateResponse{ + Response: "Hello!", + }) + }, + Setup: func(t *testing.T, req *http.Request) { + body := CompletionRequest{ + Model: "test-model", + Prompt: "Hello", + } + + bodyBytes, _ := json.Marshal(body) + + req.Body = io.NopCloser(bytes.NewReader(bodyBytes)) + req.Header.Set("Content-Type", "application/json") + }, + Expected: func(t *testing.T, resp *httptest.ResponseRecorder) { + assert.Equal(t, http.StatusOK, resp.Code) + var completionResp Completion + if err := json.NewDecoder(resp.Body).Decode(&completionResp); err != nil { + t.Fatal(err) + } + + if completionResp.Object != "text_completion" { + t.Fatalf("expected text_completion, got %s", completionResp.Object) + } + + if completionResp.Choices[0].Text != "Hello!" { + t.Fatalf("expected Hello!, got %s", completionResp.Choices[0].Text) + } + }, + }, + { + Name: "completions handler with params", + Method: http.MethodPost, + Path: "/api/generate", + TestPath: "/api/generate", + Handler: CompletionsMiddleware, + Endpoint: func(c *gin.Context) { + var generateReq api.GenerateRequest + if err := c.ShouldBindJSON(&generateReq); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "invalid request"}) + return + } + + temperature := generateReq.Options["temperature"].(float64) + var assistantMessage string + + switch temperature { + case 1.6: + assistantMessage = "Received temperature of 1.6" + default: + assistantMessage = fmt.Sprintf("Received temperature of %f", temperature) + } + + c.JSON(http.StatusOK, api.GenerateResponse{ + Response: assistantMessage, + }) + }, + Setup: func(t *testing.T, req *http.Request) { + temp := float32(0.8) + body := CompletionRequest{ + Model: "test-model", + Prompt: "Hello", + Temperature: &temp, + } + + bodyBytes, _ := json.Marshal(body) + + req.Body = io.NopCloser(bytes.NewReader(bodyBytes)) + req.Header.Set("Content-Type", "application/json") + }, + Expected: func(t *testing.T, resp *httptest.ResponseRecorder) { + assert.Equal(t, http.StatusOK, resp.Code) + var completionResp Completion + if err := json.NewDecoder(resp.Body).Decode(&completionResp); err != nil { + t.Fatal(err) + } + + if completionResp.Object != "text_completion" { + t.Fatalf("expected text_completion, got %s", completionResp.Object) + } + + if completionResp.Choices[0].Text != "Received temperature of 1.6" { + t.Fatalf("expected Received temperature of 1.6, got %s", completionResp.Choices[0].Text) + } + }, + }, + { + Name: "completions handler with error", + Method: http.MethodPost, + Path: "/api/generate", + TestPath: "/api/generate", + Handler: CompletionsMiddleware, + Endpoint: func(c *gin.Context) { + c.JSON(http.StatusBadRequest, gin.H{"error": "invalid request"}) + }, + Setup: func(t *testing.T, req *http.Request) { + body := CompletionRequest{ + Model: "test-model", + Prompt: "Hello", + } + + bodyBytes, _ := json.Marshal(body) + + req.Body = io.NopCloser(bytes.NewReader(bodyBytes)) + req.Header.Set("Content-Type", "application/json") + }, + Expected: func(t *testing.T, resp *httptest.ResponseRecorder) { + if resp.Code != http.StatusBadRequest { + t.Fatalf("expected 400, got %d", resp.Code) + } + + if !strings.Contains(resp.Body.String(), `"invalid request"`) { + t.Fatalf("error was not forwarded") + } + }, + }, { Name: "list handler", Method: http.MethodGet, @@ -99,6 +227,8 @@ func TestMiddleware(t *testing.T) { }) }, Expected: func(t *testing.T, resp *httptest.ResponseRecorder) { + assert.Equal(t, http.StatusOK, resp.Code) + var listResp ListCompletion if err := json.NewDecoder(resp.Body).Decode(&listResp); err != nil { t.Fatal(err) @@ -162,8 +292,6 @@ func TestMiddleware(t *testing.T) { resp := httptest.NewRecorder() router.ServeHTTP(resp, req) - assert.Equal(t, http.StatusOK, resp.Code) - tc.Expected(t, resp) }) } diff --git a/server/routes.go b/server/routes.go index 9fe5fcc4e..41c920844 100644 --- a/server/routes.go +++ b/server/routes.go @@ -1054,6 +1054,7 @@ func (s *Server) GenerateRoutes() http.Handler { // Compatibility endpoints r.POST("/v1/chat/completions", openai.ChatMiddleware(), s.ChatHandler) + r.POST("/v1/completions", openai.CompletionsMiddleware(), s.GenerateHandler) r.GET("/v1/models", openai.ListMiddleware(), s.ListModelsHandler) r.GET("/v1/models/:model", openai.RetrieveMiddleware(), s.ShowModelHandler) From 65a5040e09d34b4e4237a4ac1996e2fb2a112bb3 Mon Sep 17 00:00:00 2001 From: Michael Yang Date: Tue, 2 Jul 2024 16:42:17 -0700 Subject: [PATCH 013/384] fix generate template --- server/routes.go | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/server/routes.go b/server/routes.go index 41c920844..b14a146c1 100644 --- a/server/routes.go +++ b/server/routes.go @@ -176,11 +176,7 @@ func (s *Server) GenerateHandler(c *gin.Context) { prompt = req.Prompt case req.Prompt != "": if req.Template == "" { - model.Template, err = template.Parse(req.Template) - if err != nil { - c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) - return - } + tmpl = model.Template } if req.System == "" { From ef757da2c90ad52f35c95688095dfd84655cceb7 Mon Sep 17 00:00:00 2001 From: Daniel Hiltgen Date: Wed, 3 Jul 2024 10:30:07 -0700 Subject: [PATCH 014/384] Better nvidia GPU discovery logging Refine the way we log GPU discovery to improve the non-debug output, and report more actionable log messages when possible to help users troubleshoot on their own. --- docs/troubleshooting.md | 14 +++++++++----- gpu/gpu.go | 23 +++++++++++++++++++++-- gpu/gpu_info_nvcuda.c | 31 ++++++++++++++++--------------- gpu/gpu_info_nvcuda.h | 6 +++++- 4 files changed, 51 insertions(+), 23 deletions(-) diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md index de29b344c..bbb771831 100644 --- a/docs/troubleshooting.md +++ b/docs/troubleshooting.md @@ -70,14 +70,18 @@ curl -fsSL https://ollama.com/install.sh | OLLAMA_VERSION="0.1.29" sh If your system is configured with the "noexec" flag where Ollama stores its temporary executable files, you can specify an alternate location by setting OLLAMA_TMPDIR to a location writable by the user ollama runs as. For example OLLAMA_TMPDIR=/usr/share/ollama/ -## Container fails to run on NVIDIA GPU +## NVIDIA GPU Discovery -Make sure you've set up the container runtime first as described in [docker.md](./docker.md) +When Ollama starts up, it takes inventory of the GPUs present in the system to determine compatibility and how much VRAM is available. Sometimes this discovery can fail to find your GPUs. In general, running the latest driver will yield the best results. -Sometimes the container runtime can have difficulties initializing the GPU. When you check the server logs, this can show up as various error codes, such as "3" (not initialized), "46" (device unavailable), "100" (no device), "999" (unknown), or others. The following troubleshooting techniques may help resolve the problem +### Linux NVIDIA Troubleshooting -- Is the container runtime working? Try `docker run --gpus all ubuntu nvidia-smi` - if this doesn't work, Ollama wont be able to see your NVIDIA GPU. -- Is the uvm driver not loaded? `sudo nvidia-modprobe -u` +If you are using a container to run Ollama, make sure you've set up the container runtime first as described in [docker.md](./docker.md) + +Sometimes the Ollama can have difficulties initializing the GPU. When you check the server logs, this can show up as various error codes, such as "3" (not initialized), "46" (device unavailable), "100" (no device), "999" (unknown), or others. The following troubleshooting techniques may help resolve the problem + +- If you are using a container, is the container runtime working? Try `docker run --gpus all ubuntu nvidia-smi` - if this doesn't work, Ollama wont be able to see your NVIDIA GPU. +- Is the uvm driver loaded? `sudo nvidia-modprobe -u` - Try reloading the nvidia_uvm driver - `sudo rmmod nvidia_uvm` then `sudo modprobe nvidia_uvm` - Try rebooting - Make sure you're running the latest nvidia drivers diff --git a/gpu/gpu.go b/gpu/gpu.go index 583bb79c6..29a3c1037 100644 --- a/gpu/gpu.go +++ b/gpu/gpu.go @@ -202,7 +202,7 @@ func GetGPUInfo() GpuInfoList { }() if !bootstrapped { - slog.Debug("Detecting GPUs") + slog.Info("looking for compatible GPUs") needRefresh = false cpuCapability = GetCPUCapability() var memInfo C.mem_info_t @@ -320,6 +320,9 @@ func GetGPUInfo() GpuInfoList { rocmGPUs = AMDGetGPUInfo() bootstrapped = true + if len(cudaGPUs) == 0 && len(rocmGPUs) == 0 && len(oneapiGPUs) == 0 { + slog.Info("no compatible GPUs were discovered") + } } // For detected GPUs, load library if not loaded @@ -514,7 +517,23 @@ func LoadNVCUDAMgmt(nvcudaLibPaths []string) (int, *C.nvcuda_handle_t, string) { defer C.free(unsafe.Pointer(lib)) C.nvcuda_init(lib, &resp) if resp.err != nil { - slog.Debug("Unable to load nvcuda", "library", libPath, "error", C.GoString(resp.err)) + // Decide what log level based on the type of error message to help users understand why + msg := C.GoString(resp.err) + switch resp.cudaErr { + case C.CUDA_ERROR_INSUFFICIENT_DRIVER, C.CUDA_ERROR_SYSTEM_DRIVER_MISMATCH: + slog.Warn("version mismatch between driver and cuda driver library - reboot or upgrade may be required", "library", libPath, "error", msg) + case C.CUDA_ERROR_NO_DEVICE: + slog.Info("no nvidia devices detected", "library", libPath) + case C.CUDA_ERROR_UNKNOWN: + slog.Warn("unknown error initializing cuda driver library", "library", libPath, "error", msg) + slog.Warn("see https://github.com/ollama/ollama/blob/main/docs/troubleshooting.md for more information") + default: + if strings.Contains(msg, "wrong ELF class") { + slog.Debug("skipping 32bit library", "library", libPath) + } else { + slog.Info("unable to load cuda driver library", "library", libPath, "error", msg) + } + } C.free(unsafe.Pointer(resp.err)) } else { return int(resp.num_devices), &resp.ch, libPath diff --git a/gpu/gpu_info_nvcuda.c b/gpu/gpu_info_nvcuda.c index abe140844..a1a38bfc2 100644 --- a/gpu/gpu_info_nvcuda.c +++ b/gpu/gpu_info_nvcuda.c @@ -7,6 +7,7 @@ void nvcuda_init(char *nvcuda_lib_path, nvcuda_init_resp_t *resp) { CUresult ret; resp->err = NULL; resp->num_devices = 0; + resp->cudaErr = CUDA_SUCCESS; const int buflen = 256; char buf[buflen + 1]; int i; @@ -38,6 +39,7 @@ void nvcuda_init(char *nvcuda_lib_path, nvcuda_init_resp_t *resp) { nvcuda_lib_path, msg); free(msg); resp->err = strdup(buf); + resp->cudaErr = -1; return; } @@ -52,6 +54,7 @@ void nvcuda_init(char *nvcuda_lib_path, nvcuda_init_resp_t *resp) { msg); free(msg); resp->err = strdup(buf); + resp->cudaErr = -1; return; } } @@ -61,12 +64,9 @@ void nvcuda_init(char *nvcuda_lib_path, nvcuda_init_resp_t *resp) { LOG(resp->ch.verbose, "cuInit err: %d\n", ret); UNLOAD_LIBRARY(resp->ch.handle); resp->ch.handle = NULL; - if (ret == CUDA_ERROR_INSUFFICIENT_DRIVER) { - resp->err = strdup("your nvidia driver is too old or missing. If you have a CUDA GPU please upgrade to run ollama"); - return; - } - snprintf(buf, buflen, "nvcuda init failure: %d", ret); + snprintf(buf, buflen, "cuda driver library init failure: %d", ret); resp->err = strdup(buf); + resp->cudaErr = ret; return; } @@ -91,6 +91,7 @@ void nvcuda_init(char *nvcuda_lib_path, nvcuda_init_resp_t *resp) { resp->ch.handle = NULL; snprintf(buf, buflen, "unable to get device count: %d", ret); resp->err = strdup(buf); + resp->cudaErr = ret; return; } } @@ -106,13 +107,13 @@ void nvcuda_bootstrap(nvcuda_handle_t h, int i, mem_info_t *resp) { CUuuid uuid = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; if (h.handle == NULL) { - resp->err = strdup("nvcuda handle isn't initialized"); + resp->err = strdup("cuda driver library handle isn't initialized"); return; } ret = (*h.cuDeviceGet)(&device, i); if (ret != CUDA_SUCCESS) { - snprintf(buf, buflen, "nvcuda device failed to initialize"); + snprintf(buf, buflen, "cuda driver library device failed to initialize"); resp->err = strdup(buf); return; } @@ -168,14 +169,14 @@ void nvcuda_bootstrap(nvcuda_handle_t h, int i, mem_info_t *resp) { // To get memory we have to set (and release) a context ret = (*h.cuCtxCreate_v3)(&ctx, NULL, 0, 0, device); if (ret != CUDA_SUCCESS) { - snprintf(buf, buflen, "nvcuda failed to get device context %d", ret); + snprintf(buf, buflen, "cuda driver library failed to get device context %d", ret); resp->err = strdup(buf); return; } ret = (*h.cuMemGetInfo_v2)(&memInfo.free, &memInfo.total); if (ret != CUDA_SUCCESS) { - snprintf(buf, buflen, "nvcuda device memory info lookup failure %d", ret); + snprintf(buf, buflen, "cuda driver library device memory info lookup failure %d", ret); resp->err = strdup(buf); // Best effort on failure... (*h.cuCtxDestroy)(ctx); @@ -193,7 +194,7 @@ void nvcuda_bootstrap(nvcuda_handle_t h, int i, mem_info_t *resp) { ret = (*h.cuCtxDestroy)(ctx); if (ret != CUDA_SUCCESS) { - LOG(1, "nvcuda failed to release device context %d", ret); + LOG(1, "cuda driver library failed to release device context %d", ret); } } @@ -206,7 +207,7 @@ void nvcuda_get_free(nvcuda_handle_t h, int i, uint64_t *free, uint64_t *total) ret = (*h.cuDeviceGet)(&device, i); if (ret != CUDA_SUCCESS) { - LOG(1, "nvcuda device failed to initialize"); + LOG(1, "cuda driver library device failed to initialize"); return; } @@ -214,13 +215,13 @@ void nvcuda_get_free(nvcuda_handle_t h, int i, uint64_t *free, uint64_t *total) // To get memory we have to set (and release) a context ret = (*h.cuCtxCreate_v3)(&ctx, NULL, 0, 0, device); if (ret != CUDA_SUCCESS) { - LOG(1, "nvcuda failed to get device context %d", ret); + LOG(1, "cuda driver library failed to get device context %d", ret); return; } ret = (*h.cuMemGetInfo_v2)(free, total); if (ret != CUDA_SUCCESS) { - LOG(1, "nvcuda device memory info lookup failure %d", ret); + LOG(1, "cuda driver library device memory info lookup failure %d", ret); // Best effort on failure... (*h.cuCtxDestroy)(ctx); return; @@ -228,12 +229,12 @@ void nvcuda_get_free(nvcuda_handle_t h, int i, uint64_t *free, uint64_t *total) ret = (*h.cuCtxDestroy)(ctx); if (ret != CUDA_SUCCESS) { - LOG(1, "nvcuda failed to release device context %d", ret); + LOG(1, "cuda driver library failed to release device context %d", ret); } } void nvcuda_release(nvcuda_handle_t h) { - LOG(h.verbose, "releasing nvcuda library\n"); + LOG(h.verbose, "releasing cuda driver library\n"); UNLOAD_LIBRARY(h.handle); // TODO and other context release logic? h.handle = NULL; diff --git a/gpu/gpu_info_nvcuda.h b/gpu/gpu_info_nvcuda.h index f9654f641..ef2fe8a30 100644 --- a/gpu/gpu_info_nvcuda.h +++ b/gpu/gpu_info_nvcuda.h @@ -7,9 +7,12 @@ typedef enum cudaError_enum { CUDA_SUCCESS = 0, CUDA_ERROR_INVALID_VALUE = 1, - CUDA_ERROR_MEMORY_ALLOCATION = 2, + CUDA_ERROR_OUT_OF_MEMORY = 2, CUDA_ERROR_NOT_INITIALIZED = 3, CUDA_ERROR_INSUFFICIENT_DRIVER = 35, + CUDA_ERROR_NO_DEVICE = 100, + CUDA_ERROR_SYSTEM_DRIVER_MISMATCH = 803, + CUDA_ERROR_UNKNOWN = 999, // Other values omitted for now... } CUresult; @@ -64,6 +67,7 @@ typedef struct nvcuda_init_resp { char *err; // If err is non-null handle is invalid nvcuda_handle_t ch; int num_devices; + CUresult cudaErr; } nvcuda_init_resp_t; void nvcuda_init(char *nvcuda_lib_path, nvcuda_init_resp_t *resp); From 6298f49816c2264f9bb77206ad1b015aa357e381 Mon Sep 17 00:00:00 2001 From: Daniel Hiltgen Date: Wed, 3 Jul 2024 12:37:40 -0700 Subject: [PATCH 015/384] Fix clip model loading with unicode paths On windows, if the model dir contained unicode characters clip models would fail to load. This fixes the file name handling in clip.cpp to support utf16 on windows. --- llm/patches/08-clip-unicode.diff | 42 ++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 llm/patches/08-clip-unicode.diff diff --git a/llm/patches/08-clip-unicode.diff b/llm/patches/08-clip-unicode.diff new file mode 100644 index 000000000..53e5ee115 --- /dev/null +++ b/llm/patches/08-clip-unicode.diff @@ -0,0 +1,42 @@ +diff --git a/examples/llava/clip.cpp b/examples/llava/clip.cpp +index 95fbe3d0..5a02a6ec 100644 +--- a/examples/llava/clip.cpp ++++ b/examples/llava/clip.cpp +@@ -32,6 +33,14 @@ + #include + #include + ++#if defined(_WIN32) ++#define WIN32_LEAN_AND_MEAN ++#ifndef NOMINMAX ++ #define NOMINMAX ++#endif ++#include ++#endif ++ + //#define CLIP_DEBUG_FUNCTIONS + + // RGB uint8 image +@@ -1055,7 +1064,22 @@ struct clip_ctx * clip_model_load(const char * fname, const int verbosity = 1) { + return nullptr; + } + ++#ifdef _WIN32 ++ int wlen = MultiByteToWideChar(CP_UTF8, 0, fname, -1, NULL, 0); ++ if (!wlen) { ++ return NULL; ++ } ++ wchar_t * wbuf = (wchar_t *) malloc(wlen * sizeof(wchar_t)); ++ wlen = MultiByteToWideChar(CP_UTF8, 0, fname, -1, wbuf, wlen); ++ if (!wlen) { ++ free(wbuf); ++ return NULL; ++ } ++ auto fin = std::ifstream(wbuf, std::ios::binary); ++ free(wbuf); ++#else + auto fin = std::ifstream(fname, std::ios::binary); ++#endif + if (!fin) { + LOG_TEE("cannot open model file for loading tensors\n"); + clip_free(new_clip); From 0e982bc1f47cfc7c36f49f925419f9039304925e Mon Sep 17 00:00:00 2001 From: Daniel Hiltgen Date: Wed, 3 Jul 2024 13:10:14 -0700 Subject: [PATCH 016/384] Fix corner cases on tmp cleaner on mac When ollama is running a long time, tmp cleaners can remove the runners. This tightens up a few corner cases on arm macs where we failed with "server cpu not listed in available servers map[]" --- llm/payload.go | 44 +++++++++++++++++++++++--------------------- llm/server.go | 15 ++++++++++++++- 2 files changed, 37 insertions(+), 22 deletions(-) diff --git a/llm/payload.go b/llm/payload.go index 9296db336..b402e1f24 100644 --- a/llm/payload.go +++ b/llm/payload.go @@ -38,7 +38,7 @@ func Init() error { } var variants []string - for v := range availableServers() { + for v := range getAvailableServers() { variants = append(variants, v) } slog.Info(fmt.Sprintf("Dynamic LLM libraries %v", variants)) @@ -50,7 +50,7 @@ func Init() error { // binary names may contain an optional variant separated by '_' // For example, "ollama_rocm_v6" and "ollama_rocm_v5" or "ollama_cpu" and "ollama_cpu_avx2" // Any library without a variant is the lowest common denominator -func availableServers() map[string]string { +func getAvailableServers() map[string]string { payloadsDir, err := gpu.PayloadsDir() if err != nil { slog.Error("payload lookup error", "error", err) @@ -80,7 +80,7 @@ func availableServers() map[string]string { // TODO - switch to metadata based mapping func serversForGpu(info gpu.GpuInfo) []string { // glob workDir for files that start with ollama_ - availableServers := availableServers() + availableServers := getAvailableServers() requested := info.Library if info.Variant != gpu.CPUCapabilityNone { requested += "_" + info.Variant.String() @@ -115,27 +115,29 @@ func serversForGpu(info gpu.GpuInfo) []string { servers = append(servers, alt...) } - // Load up the best CPU variant if not primary requested - if info.Library != "cpu" { - variant := gpu.GetCPUCapability() - // If no variant, then we fall back to default - // If we have a variant, try that if we find an exact match - // Attempting to run the wrong CPU instructions will panic the - // process - if variant != gpu.CPUCapabilityNone { - for cmp := range availableServers { - if cmp == "cpu_"+variant.String() { - servers = append(servers, cmp) - break + if !(runtime.GOOS == "darwin" && runtime.GOARCH == "arm64") { + // Load up the best CPU variant if not primary requested + if info.Library != "cpu" { + variant := gpu.GetCPUCapability() + // If no variant, then we fall back to default + // If we have a variant, try that if we find an exact match + // Attempting to run the wrong CPU instructions will panic the + // process + if variant != gpu.CPUCapabilityNone { + for cmp := range availableServers { + if cmp == "cpu_"+variant.String() { + servers = append(servers, cmp) + break + } } + } else { + servers = append(servers, "cpu") } - } else { - servers = append(servers, "cpu") } - } - if len(servers) == 0 { - servers = []string{"cpu"} + if len(servers) == 0 { + servers = []string{"cpu"} + } } return servers @@ -147,7 +149,7 @@ func serverForCpu() string { return "metal" } variant := gpu.GetCPUCapability() - availableServers := availableServers() + availableServers := getAvailableServers() if variant != gpu.CPUCapabilityNone { for cmp := range availableServers { if cmp == "cpu_"+variant.String() { diff --git a/llm/server.go b/llm/server.go index 8b63cfbd5..4eb30e671 100644 --- a/llm/server.go +++ b/llm/server.go @@ -131,7 +131,20 @@ func NewLlamaServer(gpus gpu.GpuInfoList, model string, ggml *GGML, adapters, pr return nil, errors.New("ollama supports only one lora adapter, but multiple were provided") } - availableServers := availableServers() + availableServers := getAvailableServers() + if len(availableServers) == 0 { + if runtime.GOOS != "windows" { + slog.Warn("llama server binary disappeared, reinitializing payloads") + err = Init() + if err != nil { + slog.Warn("failed to reinitialize payloads", "error", err) + return nil, err + } + availableServers = getAvailableServers() + } else { + return nil, finalErr + } + } var servers []string if cpuRunner != "" { servers = []string{cpuRunner} From 3b5a4a77f3a191e368af3412e5de9b38b4f80771 Mon Sep 17 00:00:00 2001 From: royjhan <65097070+royjhan@users.noreply.github.com> Date: Wed, 3 Jul 2024 13:46:23 -0700 Subject: [PATCH 017/384] Return Correct Prompt Eval Count Regardless of Cache Prompt (#5371) * openai compatibility * Revert "openai compatibility" This reverts commit d3f98a811e00fc497d889c8c45b0cfec5b64690c. * remove erroneous subtraction of prompt cache --- llm/ext_server/server.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/llm/ext_server/server.cpp b/llm/ext_server/server.cpp index 3bc012521..099705998 100644 --- a/llm/ext_server/server.cpp +++ b/llm/ext_server/server.cpp @@ -1732,7 +1732,7 @@ struct llama_server_context slot.n_past -= 1; } - slot.n_prompt_tokens_processed = slot.n_prompt_tokens - slot.n_past; + slot.n_prompt_tokens_processed = slot.n_prompt_tokens; if (slot.ga_n != 1) { From 3c75113e37cc2b5d9ad8cb5c21841437aab482cc Mon Sep 17 00:00:00 2001 From: Daniel Hiltgen Date: Wed, 3 Jul 2024 14:47:42 -0700 Subject: [PATCH 018/384] Prevent loading models larger than total memory Users may not realize the siny new model they're trying to load fits on their disk, but can't load into system+GPU memory. Today we crash, but with this fix, we'll give them a better error message before even trying to load it. --- server/sched.go | 26 ++++++++++++++++++++++++++ server/sched_test.go | 12 ++++++++++++ 2 files changed, 38 insertions(+) diff --git a/server/sched.go b/server/sched.go index 71b535ae2..362430986 100644 --- a/server/sched.go +++ b/server/sched.go @@ -139,6 +139,11 @@ func (s *Scheduler) processPending(ctx context.Context) { } for { + cpus := s.getCpuFn() + var systemMem gpu.GpuInfo + if len(cpus) > 0 { + systemMem = cpus[0] + } var runnerToExpire *runnerRef s.loadedMu.Lock() runner := s.loaded[pending.model.ModelPath] @@ -192,6 +197,27 @@ func (s *Scheduler) processPending(ctx context.Context) { break } + // Block attempting to load a model larger than system memory + GPU memory + estimate := llm.EstimateGPULayers(gpus, ggml, pending.model.ProjectorPaths, pending.opts) + maxSize := systemMem.FreeMemory + for _, gpu := range gpus { + if gpu.Library == "cpu" { + continue + } + if loadedCount == 0 { + // If no other models are loaded, set the limit based on what's available + maxSize += gpu.FreeMemory + } else { + // Other models could be unloaded, favor total memory for limit + maxSize += gpu.TotalMemory + } + } + if estimate.TotalSize > maxSize { + slog.Warn("model request too large for system", "requested", format.HumanBytes2(estimate.TotalSize), "system", format.HumanBytes2(maxSize)) + pending.errCh <- fmt.Errorf("requested model (%s) is too large for this system (%s)", format.HumanBytes2(estimate.TotalSize), format.HumanBytes2(maxSize)) + break + } + // Evaluate if the model will fit in the available system memory, or if we should unload a model first if len(gpus) == 1 && gpus[0].Library == "cpu" { // simplifying assumption of defaultParallel when in CPU mode diff --git a/server/sched_test.go b/server/sched_test.go index be0830a34..83075f749 100644 --- a/server/sched_test.go +++ b/server/sched_test.go @@ -199,6 +199,8 @@ func TestRequests(t *testing.T) { require.Equal(t, resp.llama, scenario1a.srv) require.Empty(t, s.pendingReqCh) require.Empty(t, scenario1a.req.errCh) + case err := <-scenario1a.req.errCh: + t.Fatal(err.Error()) case <-ctx.Done(): t.Fatal("timeout") } @@ -212,6 +214,8 @@ func TestRequests(t *testing.T) { require.Equal(t, resp.llama, scenario1a.srv) require.Empty(t, s.pendingReqCh) require.Empty(t, scenario1b.req.errCh) + case err := <-scenario1b.req.errCh: + t.Fatal(err.Error()) case <-ctx.Done(): t.Fatal("timeout") } @@ -230,6 +234,8 @@ func TestRequests(t *testing.T) { require.Equal(t, resp.llama, scenario2a.srv) require.Empty(t, s.pendingReqCh) require.Empty(t, scenario2a.req.errCh) + case err := <-scenario2a.req.errCh: + t.Fatal(err.Error()) case <-ctx.Done(): t.Fatal("timeout") } @@ -246,6 +252,8 @@ func TestRequests(t *testing.T) { require.Equal(t, resp.llama, scenario3a.srv) require.Empty(t, s.pendingReqCh) require.Empty(t, scenario3a.req.errCh) + case err := <-scenario3a.req.errCh: + t.Fatal(err.Error()) case <-ctx.Done(): t.Fatal("timeout") } @@ -262,6 +270,8 @@ func TestRequests(t *testing.T) { require.Equal(t, resp.llama, scenario3b.srv) require.Empty(t, s.pendingReqCh) require.Empty(t, scenario3b.req.errCh) + case err := <-scenario3b.req.errCh: + t.Fatal(err.Error()) case <-ctx.Done(): t.Fatal("timeout") } @@ -278,6 +288,8 @@ func TestRequests(t *testing.T) { require.Equal(t, resp.llama, scenario3c.srv) require.Empty(t, s.pendingReqCh) require.Empty(t, scenario3c.req.errCh) + case err := <-scenario3c.req.errCh: + t.Fatal(err.Error()) case <-ctx.Done(): t.Fatal("timeout") } From 955f2a4e035044866277e26abe74343117250f1a Mon Sep 17 00:00:00 2001 From: Daniel Hiltgen Date: Tue, 2 Jul 2024 15:12:43 -0700 Subject: [PATCH 019/384] Only set default keep_alive on initial model load This change fixes the handling of keep_alive so that if client request omits the setting, we only set this on initial load. Once the model is loaded, if new requests leave this unset, we'll keep whatever keep_alive was there. --- envconfig/config.go | 31 ++++++++++++++++++++-- envconfig/config_test.go | 17 ++++++++++++ server/routes.go | 57 +++------------------------------------- server/sched.go | 14 +++++++--- server/sched_test.go | 22 ++++++++-------- 5 files changed, 70 insertions(+), 71 deletions(-) diff --git a/envconfig/config.go b/envconfig/config.go index c02c4878e..105b9af6e 100644 --- a/envconfig/config.go +++ b/envconfig/config.go @@ -4,12 +4,14 @@ import ( "errors" "fmt" "log/slog" + "math" "net" "os" "path/filepath" "runtime" "strconv" "strings" + "time" ) type OllamaHost struct { @@ -34,7 +36,7 @@ var ( // Set via OLLAMA_HOST in the environment Host *OllamaHost // Set via OLLAMA_KEEP_ALIVE in the environment - KeepAlive string + KeepAlive time.Duration // Set via OLLAMA_LLM_LIBRARY in the environment LLMLibrary string // Set via OLLAMA_MAX_LOADED_MODELS in the environment @@ -132,6 +134,7 @@ func init() { NumParallel = 0 // Autoselect MaxRunners = 0 // Autoselect MaxQueuedRequests = 512 + KeepAlive = 5 * time.Minute LoadConfig() } @@ -266,7 +269,10 @@ func LoadConfig() { } } - KeepAlive = clean("OLLAMA_KEEP_ALIVE") + ka := clean("OLLAMA_KEEP_ALIVE") + if ka != "" { + loadKeepAlive(ka) + } var err error ModelsDir, err = getModelsDir() @@ -344,3 +350,24 @@ func getOllamaHost() (*OllamaHost, error) { Port: port, }, nil } + +func loadKeepAlive(ka string) { + v, err := strconv.Atoi(ka) + if err != nil { + d, err := time.ParseDuration(ka) + if err == nil { + if d < 0 { + KeepAlive = time.Duration(math.MaxInt64) + } else { + KeepAlive = d + } + } + } else { + d := time.Duration(v) * time.Second + if d < 0 { + KeepAlive = time.Duration(math.MaxInt64) + } else { + KeepAlive = d + } + } +} diff --git a/envconfig/config_test.go b/envconfig/config_test.go index 7d923d629..a5d73fd7c 100644 --- a/envconfig/config_test.go +++ b/envconfig/config_test.go @@ -2,8 +2,10 @@ package envconfig import ( "fmt" + "math" "net" "testing" + "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -23,6 +25,21 @@ func TestConfig(t *testing.T) { t.Setenv("OLLAMA_FLASH_ATTENTION", "1") LoadConfig() require.True(t, FlashAttention) + t.Setenv("OLLAMA_KEEP_ALIVE", "") + LoadConfig() + require.Equal(t, 5*time.Minute, KeepAlive) + t.Setenv("OLLAMA_KEEP_ALIVE", "3") + LoadConfig() + require.Equal(t, 3*time.Second, KeepAlive) + t.Setenv("OLLAMA_KEEP_ALIVE", "1h") + LoadConfig() + require.Equal(t, 1*time.Hour, KeepAlive) + t.Setenv("OLLAMA_KEEP_ALIVE", "-1s") + LoadConfig() + require.Equal(t, time.Duration(math.MaxInt64), KeepAlive) + t.Setenv("OLLAMA_KEEP_ALIVE", "-1") + LoadConfig() + require.Equal(t, time.Duration(math.MaxInt64), KeepAlive) } func TestClientFromEnvironment(t *testing.T) { diff --git a/server/routes.go b/server/routes.go index b14a146c1..ac6b713a7 100644 --- a/server/routes.go +++ b/server/routes.go @@ -9,7 +9,6 @@ import ( "io" "io/fs" "log/slog" - "math" "net" "net/http" "net/netip" @@ -17,7 +16,6 @@ import ( "os/signal" "path/filepath" "slices" - "strconv" "strings" "syscall" "time" @@ -56,8 +54,6 @@ func init() { gin.SetMode(mode) } -var defaultSessionDuration = 5 * time.Minute - func modelOptions(model *Model, requestOpts map[string]interface{}) (api.Options, error) { opts := api.DefaultOptions() if err := opts.FromMap(model.Options); err != nil { @@ -133,14 +129,7 @@ func (s *Server) GenerateHandler(c *gin.Context) { return } - var sessionDuration time.Duration - if req.KeepAlive == nil { - sessionDuration = getDefaultSessionDuration() - } else { - sessionDuration = req.KeepAlive.Duration - } - - rCh, eCh := s.sched.GetRunner(c.Request.Context(), model, opts, sessionDuration) + rCh, eCh := s.sched.GetRunner(c.Request.Context(), model, opts, req.KeepAlive) var runner *runnerRef select { case runner = <-rCh: @@ -320,32 +309,6 @@ func (s *Server) GenerateHandler(c *gin.Context) { streamResponse(c, ch) } -func getDefaultSessionDuration() time.Duration { - if envconfig.KeepAlive != "" { - v, err := strconv.Atoi(envconfig.KeepAlive) - if err != nil { - d, err := time.ParseDuration(envconfig.KeepAlive) - if err != nil { - return defaultSessionDuration - } - - if d < 0 { - return time.Duration(math.MaxInt64) - } - - return d - } - - d := time.Duration(v) * time.Second - if d < 0 { - return time.Duration(math.MaxInt64) - } - return d - } - - return defaultSessionDuration -} - func (s *Server) EmbeddingsHandler(c *gin.Context) { var req api.EmbeddingRequest err := c.ShouldBindJSON(&req) @@ -380,14 +343,7 @@ func (s *Server) EmbeddingsHandler(c *gin.Context) { return } - var sessionDuration time.Duration - if req.KeepAlive == nil { - sessionDuration = getDefaultSessionDuration() - } else { - sessionDuration = req.KeepAlive.Duration - } - - rCh, eCh := s.sched.GetRunner(c.Request.Context(), model, opts, sessionDuration) + rCh, eCh := s.sched.GetRunner(c.Request.Context(), model, opts, req.KeepAlive) var runner *runnerRef select { case runner = <-rCh: @@ -1318,14 +1274,7 @@ func (s *Server) ChatHandler(c *gin.Context) { return } - var sessionDuration time.Duration - if req.KeepAlive == nil { - sessionDuration = getDefaultSessionDuration() - } else { - sessionDuration = req.KeepAlive.Duration - } - - rCh, eCh := s.sched.GetRunner(c.Request.Context(), model, opts, sessionDuration) + rCh, eCh := s.sched.GetRunner(c.Request.Context(), model, opts, req.KeepAlive) var runner *runnerRef select { case runner = <-rCh: diff --git a/server/sched.go b/server/sched.go index 71b535ae2..dc492cfb3 100644 --- a/server/sched.go +++ b/server/sched.go @@ -24,7 +24,7 @@ type LlmRequest struct { model *Model opts api.Options origNumCtx int // Track the initial ctx request - sessionDuration time.Duration + sessionDuration *api.Duration successCh chan *runnerRef errCh chan error schedAttempts uint @@ -75,7 +75,7 @@ func InitScheduler(ctx context.Context) *Scheduler { } // context must be canceled to decrement ref count and release the runner -func (s *Scheduler) GetRunner(c context.Context, model *Model, opts api.Options, sessionDuration time.Duration) (chan *runnerRef, chan error) { +func (s *Scheduler) GetRunner(c context.Context, model *Model, opts api.Options, sessionDuration *api.Duration) (chan *runnerRef, chan error) { if opts.NumCtx < 4 { opts.NumCtx = 4 } @@ -389,7 +389,9 @@ func (pending *LlmRequest) useLoadedRunner(runner *runnerRef, finished chan *Llm runner.expireTimer.Stop() runner.expireTimer = nil } - runner.sessionDuration = pending.sessionDuration + if pending.sessionDuration != nil { + runner.sessionDuration = pending.sessionDuration.Duration + } pending.successCh <- runner go func() { <-pending.ctx.Done() @@ -402,6 +404,10 @@ func (s *Scheduler) load(req *LlmRequest, ggml *llm.GGML, gpus gpu.GpuInfoList, if numParallel < 1 { numParallel = 1 } + sessionDuration := envconfig.KeepAlive + if req.sessionDuration != nil { + sessionDuration = req.sessionDuration.Duration + } llama, err := s.newServerFn(gpus, req.model.ModelPath, ggml, req.model.AdapterPaths, req.model.ProjectorPaths, req.opts, numParallel) if err != nil { // some older models are not compatible with newer versions of llama.cpp @@ -419,7 +425,7 @@ func (s *Scheduler) load(req *LlmRequest, ggml *llm.GGML, gpus gpu.GpuInfoList, modelPath: req.model.ModelPath, llama: llama, Options: &req.opts, - sessionDuration: req.sessionDuration, + sessionDuration: sessionDuration, gpus: gpus, estimatedVRAM: llama.EstimatedVRAM(), estimatedTotal: llama.EstimatedTotal(), diff --git a/server/sched_test.go b/server/sched_test.go index be0830a34..d957927e6 100644 --- a/server/sched_test.go +++ b/server/sched_test.go @@ -44,7 +44,7 @@ func TestLoad(t *testing.T) { opts: api.DefaultOptions(), successCh: make(chan *runnerRef, 1), errCh: make(chan error, 1), - sessionDuration: 2, + sessionDuration: &api.Duration{Duration: 2 * time.Second}, } // Fail to load model first s.newServerFn = func(gpus gpu.GpuInfoList, model string, ggml *llm.GGML, adapters []string, projectors []string, opts api.Options, numParallel int) (llm.LlamaServer, error) { @@ -142,7 +142,7 @@ func newScenario(t *testing.T, ctx context.Context, modelName string, estimatedV ctx: scenario.ctx, model: model, opts: api.DefaultOptions(), - sessionDuration: 5 * time.Millisecond, + sessionDuration: &api.Duration{Duration: 5 * time.Millisecond}, successCh: make(chan *runnerRef, 1), errCh: make(chan error, 1), } @@ -156,18 +156,18 @@ func TestRequests(t *testing.T) { // Same model, same request scenario1a := newScenario(t, ctx, "ollama-model-1", 10) - scenario1a.req.sessionDuration = 5 * time.Millisecond + scenario1a.req.sessionDuration = &api.Duration{Duration: 5 * time.Millisecond} scenario1b := newScenario(t, ctx, "ollama-model-1", 11) scenario1b.req.model = scenario1a.req.model scenario1b.ggml = scenario1a.ggml - scenario1b.req.sessionDuration = 0 + scenario1b.req.sessionDuration = &api.Duration{Duration: 0} // simple reload of same model scenario2a := newScenario(t, ctx, "ollama-model-1", 20) tmpModel := *scenario1a.req.model scenario2a.req.model = &tmpModel scenario2a.ggml = scenario1a.ggml - scenario2a.req.sessionDuration = 5 * time.Millisecond + scenario2a.req.sessionDuration = &api.Duration{Duration: 5 * time.Millisecond} // Multiple loaded models scenario3a := newScenario(t, ctx, "ollama-model-3a", 1*format.GigaByte) @@ -318,11 +318,11 @@ func TestGetRunner(t *testing.T) { defer done() scenario1a := newScenario(t, ctx, "ollama-model-1a", 10) - scenario1a.req.sessionDuration = 0 + scenario1a.req.sessionDuration = &api.Duration{Duration: 0} scenario1b := newScenario(t, ctx, "ollama-model-1b", 10) - scenario1b.req.sessionDuration = 0 + scenario1b.req.sessionDuration = &api.Duration{Duration: 0} scenario1c := newScenario(t, ctx, "ollama-model-1c", 10) - scenario1c.req.sessionDuration = 0 + scenario1c.req.sessionDuration = &api.Duration{Duration: 0} envconfig.MaxQueuedRequests = 1 s := InitScheduler(ctx) s.getGpuFn = func() gpu.GpuInfoList { @@ -402,7 +402,7 @@ func TestPrematureExpired(t *testing.T) { case <-ctx.Done(): t.Fatal("timeout") } - time.Sleep(scenario1a.req.sessionDuration) + time.Sleep(scenario1a.req.sessionDuration.Duration) scenario1a.ctxDone() time.Sleep(20 * time.Millisecond) require.LessOrEqual(t, len(s.finishedReqCh), 1) @@ -423,7 +423,7 @@ func TestUseLoadedRunner(t *testing.T) { ctx: ctx, opts: api.DefaultOptions(), successCh: make(chan *runnerRef, 1), - sessionDuration: 2, + sessionDuration: &api.Duration{Duration: 2}, } finished := make(chan *LlmRequest) llm1 := &mockLlm{estimatedVRAMByGPU: map[string]uint64{}} @@ -614,7 +614,7 @@ func TestAlreadyCanceled(t *testing.T) { dctx, done2 := context.WithCancel(ctx) done2() scenario1a := newScenario(t, dctx, "ollama-model-1", 10) - scenario1a.req.sessionDuration = 0 + scenario1a.req.sessionDuration = &api.Duration{Duration: 0} s := InitScheduler(ctx) slog.Info("scenario1a") s.pendingReqCh <- scenario1a.req From 0d16eb310ed26e5a438f482dbffe7687e106346e Mon Sep 17 00:00:00 2001 From: Anatoli Babenia Date: Thu, 4 Jul 2024 01:36:11 +0300 Subject: [PATCH 020/384] fix: use `envconfig.ModelsDir` directly (#4821) * Co-authored-by: Anatoli Babenia Co-authored-by: Maas Lalani --- envconfig/config.go | 4 ++-- server/modelpath.go | 21 +++------------------ 2 files changed, 5 insertions(+), 20 deletions(-) diff --git a/envconfig/config.go b/envconfig/config.go index 105b9af6e..62d661ebc 100644 --- a/envconfig/config.go +++ b/envconfig/config.go @@ -43,10 +43,10 @@ var ( MaxRunners int // Set via OLLAMA_MAX_QUEUE in the environment MaxQueuedRequests int - // Set via OLLAMA_MODELS in the environment - ModelsDir string // Set via OLLAMA_MAX_VRAM in the environment MaxVRAM uint64 + // Set via OLLAMA_MODELS in the environment + ModelsDir string // Set via OLLAMA_NOHISTORY in the environment NoHistory bool // Set via OLLAMA_NOPRUNE in the environment diff --git a/server/modelpath.go b/server/modelpath.go index 64f59c29a..3fdb4238f 100644 --- a/server/modelpath.go +++ b/server/modelpath.go @@ -103,18 +103,9 @@ func (mp ModelPath) GetShortTagname() string { return fmt.Sprintf("%s/%s/%s:%s", mp.Registry, mp.Namespace, mp.Repository, mp.Tag) } -// modelsDir returns the value of the OLLAMA_MODELS environment variable or the user's home directory if OLLAMA_MODELS is not set. -// The models directory is where Ollama stores its model files and manifests. -func modelsDir() (string, error) { - return envconfig.ModelsDir, nil -} - // GetManifestPath returns the path to the manifest file for the given model path, it is up to the caller to create the directory if it does not exist. func (mp ModelPath) GetManifestPath() (string, error) { - dir, err := modelsDir() - if err != nil { - return "", err - } + dir := envconfig.ModelsDir return filepath.Join(dir, "manifests", mp.Registry, mp.Namespace, mp.Repository, mp.Tag), nil } @@ -127,10 +118,7 @@ func (mp ModelPath) BaseURL() *url.URL { } func GetManifestPath() (string, error) { - dir, err := modelsDir() - if err != nil { - return "", err - } + dir := envconfig.ModelsDir path := filepath.Join(dir, "manifests") if err := os.MkdirAll(path, 0o755); err != nil { @@ -141,10 +129,7 @@ func GetManifestPath() (string, error) { } func GetBlobsPath(digest string) (string, error) { - dir, err := modelsDir() - if err != nil { - return "", err - } + dir := envconfig.ModelsDir // only accept actual sha256 digests pattern := "^sha256[:-][0-9a-fA-F]{64}$" From 4d71c559b21ec9207a328b824ce534bdbaf59f2d Mon Sep 17 00:00:00 2001 From: Jeffrey Morgan Date: Wed, 3 Jul 2024 20:04:30 -0400 Subject: [PATCH 021/384] fix error detection by limiting model loading error parsing (#5472) --- llm/status.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/llm/status.go b/llm/status.go index 0f56b7f99..d9f361155 100644 --- a/llm/status.go +++ b/llm/status.go @@ -25,7 +25,7 @@ var errorPrefixes = []string{ "CUDA error", "cudaMalloc failed", "\"ERR\"", - "architecture", + "error loading model", } func (w *StatusWriter) Write(b []byte) (int, error) { From 52abc8acb702cad0b58cee92721e64687f5a6c85 Mon Sep 17 00:00:00 2001 From: Daniel Hiltgen Date: Mon, 13 May 2024 15:08:29 -0700 Subject: [PATCH 022/384] Document older win10 terminal problems We haven't found a workaround, so for now recommend updating. --- docs/troubleshooting.md | 5 +++++ docs/windows.md | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md index bbb771831..484c4b6ce 100644 --- a/docs/troubleshooting.md +++ b/docs/troubleshooting.md @@ -89,3 +89,8 @@ Sometimes the Ollama can have difficulties initializing the GPU. When you check If none of those resolve the problem, gather additional information and file an issue: - Set `CUDA_ERROR_LEVEL=50` and try again to get more diagnostic logs - Check dmesg for any errors `sudo dmesg | grep -i nvrm` and `sudo dmesg | grep -i nvidia` + + +## Windows Terminal Errors + +Older versions of Windows 10 (e.g., 21H1) are known to have a bug where the standard terminal program does not display control characters correctly. This can result in a long string of strings like `←[?25h←[?25l` being displayed, sometimes erroring with `The parameter is incorrect` To resolve this problem, please update to Win 10 22H1 or newer. diff --git a/docs/windows.md b/docs/windows.md index abc0eb300..69c2aa6d1 100644 --- a/docs/windows.md +++ b/docs/windows.md @@ -19,7 +19,7 @@ Logs will often be helpful in diagnosing the problem (see ## System Requirements -* Windows 10 or newer, Home or Pro +* Windows 10 22H2 or newer, Home or Pro * NVIDIA 452.39 or newer Drivers if you have an NVIDIA card * AMD Radeon Driver https://www.amd.com/en/support if you have a Radeon card From e9188e971a998faff7aabd867ebc0ef1dc7f672b Mon Sep 17 00:00:00 2001 From: Jeffrey Morgan Date: Fri, 5 Jul 2024 11:20:57 -0400 Subject: [PATCH 023/384] Fix assert on small embedding inputs (#5491) * Fix assert on small embedding inputs * Update llm/patches/09-pooling.diff --- llm/patches/09-pooling.diff | 60 +++++++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 llm/patches/09-pooling.diff diff --git a/llm/patches/09-pooling.diff b/llm/patches/09-pooling.diff new file mode 100644 index 000000000..348fbfdc4 --- /dev/null +++ b/llm/patches/09-pooling.diff @@ -0,0 +1,60 @@ +diff --git a/llama.cpp b/llama.cpp +index 61948751..61fe7b57 100644 +--- a/llama.cpp ++++ b/llama.cpp +@@ -7591,14 +7591,14 @@ struct llm_build_context { + } + + struct ggml_tensor * build_inp_mean() { +- lctx.inp_mean = ggml_new_tensor_2d(ctx0, GGML_TYPE_F32, n_tokens, n_tokens); ++ lctx.inp_mean = ggml_new_tensor_2d(ctx0, GGML_TYPE_F32, n_tokens, cparams.n_seq_max); + cb(lctx.inp_mean, "inp_mean", -1); + ggml_set_input(lctx.inp_mean); + return lctx.inp_mean; + } + + struct ggml_tensor * build_inp_cls() { +- lctx.inp_cls = ggml_new_tensor_1d(ctx0, GGML_TYPE_I32, n_tokens); ++ lctx.inp_cls = ggml_new_tensor_1d(ctx0, GGML_TYPE_I32, cparams.n_seq_max); + cb(lctx.inp_cls, "inp_cls", -1); + ggml_set_input(lctx.inp_cls); + return lctx.inp_cls; +@@ -12062,19 +12062,16 @@ static void llama_set_inputs(llama_context & lctx, const llama_batch & batch) { + GGML_ASSERT(ggml_backend_buffer_is_host(lctx.inp_mean->buffer)); + + float * data = (float *) lctx.inp_mean->data; +- memset(lctx.inp_mean->data, 0, n_tokens * n_tokens * ggml_element_size(lctx.inp_mean)); ++ memset(lctx.inp_mean->data, 0, n_tokens * cparams.n_seq_max * ggml_element_size(lctx.inp_mean)); + + std::vector sum(n_tokens, 0); + for (int i = 0; i < n_tokens; ++i) { + const llama_seq_id seq_id = batch.seq_id[i][0]; +- +- GGML_ASSERT(seq_id < n_tokens && "seq_id cannot be larger than n_tokens with pooling_type == MEAN"); +- + sum[seq_id] += 1; + } + +- std::vector div(n_tokens, 0.0f); +- for (int i = 0; i < n_tokens; ++i) { ++ std::vector div(cparams.n_seq_max, 0.0f); ++ for (uint32_t i = 0; i < cparams.n_seq_max; ++i) { + const uint64_t s = sum[i]; + if (s > 0) { + div[i] = 1.0f/float(s); +@@ -12094,14 +12091,11 @@ static void llama_set_inputs(llama_context & lctx, const llama_batch & batch) { + GGML_ASSERT(ggml_backend_buffer_is_host(lctx.inp_cls->buffer)); + + uint32_t * data = (uint32_t *) lctx.inp_cls->data; +- memset(lctx.inp_cls->data, 0, n_tokens * ggml_element_size(lctx.inp_cls)); ++ memset(lctx.inp_cls->data, 0, cparams.n_seq_max * ggml_element_size(lctx.inp_cls)); + + for (int i = 0; i < n_tokens; ++i) { + const llama_seq_id seq_id = batch.seq_id[i][0]; + const llama_pos pos = batch.pos[i]; +- +- GGML_ASSERT(seq_id < n_tokens && "seq_id cannot be larger than n_tokens with pooling_type == CLS"); +- + if (pos == 0) { + data[seq_id] = i; + } From d89454de805c6d9507796cf2a262986db43ed849 Mon Sep 17 00:00:00 2001 From: Jeffrey Morgan Date: Fri, 5 Jul 2024 12:32:47 -0400 Subject: [PATCH 024/384] Use slot with cached prompt instead of least recently used (#5492) * Use common prefix to select slot * actually report `longest` --- llm/ext_server/server.cpp | 40 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/llm/ext_server/server.cpp b/llm/ext_server/server.cpp index 099705998..00a15b4a3 100644 --- a/llm/ext_server/server.cpp +++ b/llm/ext_server/server.cpp @@ -1382,12 +1382,50 @@ struct llama_server_context } } + std::string common_prefix(const std::string& str1, const std::string& str2) { + auto mismatch_pair = std::mismatch(str1.begin(), str1.end(), str2.begin()); + return std::string(str1.begin(), mismatch_pair.first); + } + + // Find the slot that has the greatest common prefix + server_slot *prefix_slot(const json &prompt) { + if (!prompt.is_string()) { + return nullptr; + } + + std::string prompt_str = prompt.get(); + server_slot *slot = nullptr; + size_t longest = 0; + + for (server_slot &s : slots) { + if (s.available() && s.prompt.is_string()) { + std::string s_prompt = s.prompt.get(); + std::string prefix = common_prefix(s_prompt, prompt_str); + + if (prefix.size() > longest) { + slot = &s; + longest = prefix.size(); + } + } + } + + if (!slot) { + return get_slot(-1); + } + + LOG_INFO("slot with common prefix found", {{ + "slot_id", slot->id, + "characters", longest + }}); + return slot; + } + void process_single_task(task_server& task) { switch (task.type) { case TASK_TYPE_COMPLETION: { - server_slot *slot = get_slot(json_value(task.data, "slot_id", -1)); + server_slot *slot = prefix_slot(task.data["prompt"]); if (slot == nullptr) { // if no slot is available, we defer this task for processing later From 8f8e736b131510c8707bed5886b343906cb74a24 Mon Sep 17 00:00:00 2001 From: Jeffrey Morgan Date: Fri, 5 Jul 2024 13:25:58 -0400 Subject: [PATCH 025/384] update llama.cpp submodule to `d7fd29f` (#5475) --- docs/development.md | 2 +- llm/ext_server/CMakeLists.txt | 26 +- llm/generate/gen_darwin.sh | 16 +- llm/generate/gen_linux.sh | 36 +-- llm/generate/gen_windows.ps1 | 44 ++-- llm/llama.cpp | 2 +- llm/llm.go | 14 +- llm/patches/01-load-progress.diff | 14 +- llm/patches/03-load_exception.diff | 24 +- llm/patches/04-metal.diff | 6 +- llm/patches/05-default-pretokenizer.diff | 18 +- llm/patches/06-qwen2.diff | 6 +- llm/patches/07-embeddings.diff | 45 ++++ llm/patches/07-gemma.diff | 305 ----------------------- llm/patches/09-pooling.diff | 14 +- 15 files changed, 150 insertions(+), 422 deletions(-) create mode 100644 llm/patches/07-embeddings.diff delete mode 100644 llm/patches/07-gemma.diff diff --git a/docs/development.md b/docs/development.md index 2a6886a43..cd6c41af5 100644 --- a/docs/development.md +++ b/docs/development.md @@ -104,7 +104,7 @@ like to use. For example, to compile an optimized binary for an Intel i9-9880H, you might use: ``` -OLLAMA_CUSTOM_CPU_DEFS="-DLLAMA_AVX=on -DLLAMA_AVX2=on -DLLAMA_F16C=on -DLLAMA_FMA=on" go generate ./... +OLLAMA_CUSTOM_CPU_DEFS="-DGGML_AVX=on -DGGML_AVX2=on -DGGML_F16C=on -DGGML_FMA=on" go generate ./... go build . ``` diff --git a/llm/ext_server/CMakeLists.txt b/llm/ext_server/CMakeLists.txt index db7d52dcc..9de50739c 100644 --- a/llm/ext_server/CMakeLists.txt +++ b/llm/ext_server/CMakeLists.txt @@ -1,14 +1,14 @@ - -set(TARGET ollama_llama_server) -option(LLAMA_SERVER_VERBOSE "Build verbose logging option for Server" ON) -include_directories(${CMAKE_CURRENT_SOURCE_DIR}) -add_executable(${TARGET} server.cpp utils.hpp json.hpp httplib.h) -install(TARGETS ${TARGET} RUNTIME) -target_compile_definitions(${TARGET} PRIVATE - SERVER_VERBOSE=$ -) -target_link_libraries(${TARGET} PRIVATE common llava ${CMAKE_THREAD_LIBS_INIT}) -if (WIN32) - TARGET_LINK_LIBRARIES(${TARGET} PRIVATE ws2_32) -endif() + +set(TARGET ollama_llama_server) +option(LLAMA_SERVER_VERBOSE "Build verbose logging option for Server" ON) +include_directories(${CMAKE_CURRENT_SOURCE_DIR}) +add_executable(${TARGET} server.cpp utils.hpp json.hpp httplib.h) +install(TARGETS ${TARGET} RUNTIME) +target_compile_definitions(${TARGET} PRIVATE + SERVER_VERBOSE=$ +) +target_link_libraries(${TARGET} PRIVATE ggml llama common llava ${CMAKE_THREAD_LIBS_INIT}) +if (WIN32) + TARGET_LINK_LIBRARIES(${TARGET} PRIVATE ws2_32) +endif() target_compile_features(${TARGET} PRIVATE cxx_std_11) \ No newline at end of file diff --git a/llm/generate/gen_darwin.sh b/llm/generate/gen_darwin.sh index 721a9ae80..02577545a 100755 --- a/llm/generate/gen_darwin.sh +++ b/llm/generate/gen_darwin.sh @@ -18,16 +18,16 @@ sign() { fi } -COMMON_DARWIN_DEFS="-DCMAKE_OSX_DEPLOYMENT_TARGET=11.3 -DLLAMA_METAL_MACOSX_VERSION_MIN=11.3 -DCMAKE_SYSTEM_NAME=Darwin -DLLAMA_METAL_EMBED_LIBRARY=on -DLLAMA_OPENMP=off" +COMMON_DARWIN_DEFS="-DCMAKE_OSX_DEPLOYMENT_TARGET=11.3 -DLLAMA_METAL_MACOSX_VERSION_MIN=11.3 -DCMAKE_SYSTEM_NAME=Darwin -DGGML_METAL_EMBED_LIBRARY=on -DGGML_OPENMP=off" case "${GOARCH}" in "amd64") - COMMON_CPU_DEFS="${COMMON_DARWIN_DEFS} -DCMAKE_SYSTEM_PROCESSOR=${ARCH} -DCMAKE_OSX_ARCHITECTURES=${ARCH} -DLLAMA_METAL=off -DLLAMA_NATIVE=off" + COMMON_CPU_DEFS="${COMMON_DARWIN_DEFS} -DCMAKE_SYSTEM_PROCESSOR=${ARCH} -DCMAKE_OSX_ARCHITECTURES=${ARCH} -DGGML_METAL=off -DGGML_NATIVE=off" # Static build for linking into the Go binary init_vars CMAKE_TARGETS="--target llama --target ggml" - CMAKE_DEFS="${COMMON_CPU_DEFS} -DBUILD_SHARED_LIBS=off -DLLAMA_BLAS=off -DLLAMA_ACCELERATE=off -DLLAMA_AVX=off -DLLAMA_AVX2=off -DLLAMA_AVX512=off -DLLAMA_FMA=off -DLLAMA_F16C=off ${CMAKE_DEFS}" + CMAKE_DEFS="${COMMON_CPU_DEFS} -DBUILD_SHARED_LIBS=off -DGGML_BLAS=off -DGGML_ACCELERATE=off -DGGML_AVX=off -DGGML_AVX2=off -DGGML_AVX512=off -DGGML_FMA=off -DGGML_F16C=off ${CMAKE_DEFS}" BUILD_DIR="../build/darwin/${ARCH}_static" echo "Building static library" build @@ -37,7 +37,7 @@ case "${GOARCH}" in # CPU first for the default library, set up as lowest common denominator for maximum compatibility (including Rosetta) # init_vars - CMAKE_DEFS="${COMMON_CPU_DEFS} -DLLAMA_ACCELERATE=off -DLLAMA_BLAS=off -DLLAMA_AVX=off -DLLAMA_AVX2=off -DLLAMA_AVX512=off -DLLAMA_FMA=off -DLLAMA_F16C=off ${CMAKE_DEFS}" + CMAKE_DEFS="${COMMON_CPU_DEFS} -DGGML_ACCELERATE=off -DGGML_BLAS=off -DGGML_AVX=off -DGGML_AVX2=off -DGGML_AVX512=off -DGGML_FMA=off -DGGML_F16C=off ${CMAKE_DEFS}" BUILD_DIR="../build/darwin/${ARCH}/cpu" echo "Building LCD CPU" build @@ -49,7 +49,7 @@ case "${GOARCH}" in # Approximately 400% faster than LCD on same CPU # init_vars - CMAKE_DEFS="${COMMON_CPU_DEFS} -DLLAMA_ACCELERATE=off -DLLAMA_BLAS=off -DLLAMA_AVX=on -DLLAMA_AVX2=off -DLLAMA_AVX512=off -DLLAMA_FMA=off -DLLAMA_F16C=off ${CMAKE_DEFS}" + CMAKE_DEFS="${COMMON_CPU_DEFS} -DGGML_ACCELERATE=off -DGGML_BLAS=off -DGGML_AVX=on -DGGML_AVX2=off -DGGML_AVX512=off -DGGML_FMA=off -DGGML_F16C=off ${CMAKE_DEFS}" BUILD_DIR="../build/darwin/${ARCH}/cpu_avx" echo "Building AVX CPU" build @@ -61,7 +61,7 @@ case "${GOARCH}" in # Approximately 10% faster than AVX on same CPU # init_vars - CMAKE_DEFS="${COMMON_CPU_DEFS} -DLLAMA_ACCELERATE=on -DLLAMA_BLAS=off -DLLAMA_AVX=on -DLLAMA_AVX2=on -DLLAMA_AVX512=off -DLLAMA_FMA=on -DLLAMA_F16C=on ${CMAKE_DEFS}" + CMAKE_DEFS="${COMMON_CPU_DEFS} -DGGML_ACCELERATE=on -DGGML_BLAS=off -DGGML_AVX=on -DGGML_AVX2=on -DGGML_AVX512=off -DGGML_FMA=on -DGGML_F16C=on ${CMAKE_DEFS}" BUILD_DIR="../build/darwin/${ARCH}/cpu_avx2" echo "Building AVX2 CPU" EXTRA_LIBS="${EXTRA_LIBS} -framework Accelerate -framework Foundation" @@ -75,14 +75,14 @@ case "${GOARCH}" in # Static build for linking into the Go binary init_vars CMAKE_TARGETS="--target llama --target ggml" - CMAKE_DEFS="-DCMAKE_OSX_DEPLOYMENT_TARGET=11.3 -DLLAMA_BLAS=off -DCMAKE_SYSTEM_NAME=Darwin -DBUILD_SHARED_LIBS=off -DCMAKE_SYSTEM_PROCESSOR=${ARCH} -DCMAKE_OSX_ARCHITECTURES=${ARCH} -DLLAMA_METAL=off -DLLAMA_ACCELERATE=off -DLLAMA_AVX=off -DLLAMA_AVX2=off -DLLAMA_AVX512=off -DLLAMA_FMA=off -DLLAMA_F16C=off ${CMAKE_DEFS}" + CMAKE_DEFS="-DCMAKE_OSX_DEPLOYMENT_TARGET=11.3 -DCMAKE_SYSTEM_NAME=Darwin -DBUILD_SHARED_LIBS=off -DCMAKE_SYSTEM_PROCESSOR=${ARCH} -DCMAKE_OSX_ARCHITECTURES=${ARCH} ${CMAKE_DEFS}" BUILD_DIR="../build/darwin/${ARCH}_static" echo "Building static library" build if [ -z "$OLLAMA_SKIP_METAL_GENERATE" ]; then init_vars - CMAKE_DEFS="${COMMON_DARWIN_DEFS} -DLLAMA_ACCELERATE=on -DCMAKE_SYSTEM_PROCESSOR=${ARCH} -DCMAKE_OSX_ARCHITECTURES=${ARCH} -DLLAMA_METAL=on ${CMAKE_DEFS}" + CMAKE_DEFS="${COMMON_DARWIN_DEFS} -DCMAKE_SYSTEM_PROCESSOR=${ARCH} -DCMAKE_OSX_ARCHITECTURES=${ARCH} ${CMAKE_DEFS}" BUILD_DIR="../build/darwin/${ARCH}/metal" EXTRA_LIBS="${EXTRA_LIBS} -framework Accelerate -framework Foundation -framework Metal -framework MetalKit -framework MetalPerformanceShaders" build diff --git a/llm/generate/gen_linux.sh b/llm/generate/gen_linux.sh index 28ce1f21d..c36862520 100755 --- a/llm/generate/gen_linux.sh +++ b/llm/generate/gen_linux.sh @@ -51,7 +51,7 @@ if [ -z "${CUDACXX}" ]; then export CUDACXX=$(command -v nvcc) fi fi -COMMON_CMAKE_DEFS="-DCMAKE_POSITION_INDEPENDENT_CODE=on -DLLAMA_NATIVE=off -DLLAMA_AVX=on -DLLAMA_AVX2=off -DLLAMA_AVX512=off -DLLAMA_FMA=off -DLLAMA_F16C=off -DLLAMA_OPENMP=off" +COMMON_CMAKE_DEFS="-DCMAKE_POSITION_INDEPENDENT_CODE=on -DGGML_NATIVE=off -DGGML_AVX=on -DGGML_AVX2=off -DGGML_AVX512=off -DGGML_FMA=off -DGGML_F16C=off -DGGML_OPENMP=off" source $(dirname $0)/gen_common.sh init_vars git_module_setup @@ -64,7 +64,7 @@ if [ -z "${OLLAMA_SKIP_STATIC_GENERATE}" -o "${OLLAMA_CPU_TARGET}" = "static" ]; # Static build for linking into the Go binary init_vars CMAKE_TARGETS="--target llama --target ggml" - CMAKE_DEFS="-DBUILD_SHARED_LIBS=off -DLLAMA_NATIVE=off -DLLAMA_AVX=off -DLLAMA_AVX2=off -DLLAMA_AVX512=off -DLLAMA_FMA=off -DLLAMA_F16C=off -DLLAMA_OPENMP=off ${CMAKE_DEFS}" + CMAKE_DEFS="-DBUILD_SHARED_LIBS=off -DGGML_NATIVE=off -DGGML_AVX=off -DGGML_AVX2=off -DGGML_AVX512=off -DGGML_FMA=off -DGGML_F16C=off -DGGML_OPENMP=off ${CMAKE_DEFS}" BUILD_DIR="../build/linux/${ARCH}_static" echo "Building static library" build @@ -84,22 +84,22 @@ if [ -z "${OLLAMA_SKIP_CPU_GENERATE}" ]; then compress else # Darwin Rosetta x86 emulation does NOT support AVX, AVX2, AVX512 - # -DLLAMA_AVX -- 2011 Intel Sandy Bridge & AMD Bulldozer - # -DLLAMA_F16C -- 2012 Intel Ivy Bridge & AMD 2011 Bulldozer (No significant improvement over just AVX) - # -DLLAMA_AVX2 -- 2013 Intel Haswell & 2015 AMD Excavator / 2017 AMD Zen - # -DLLAMA_FMA (FMA3) -- 2013 Intel Haswell & 2012 AMD Piledriver + # -DGGML_AVX -- 2011 Intel Sandy Bridge & AMD Bulldozer + # -DGGML_F16C -- 2012 Intel Ivy Bridge & AMD 2011 Bulldozer (No significant improvement over just AVX) + # -DGGML_AVX2 -- 2013 Intel Haswell & 2015 AMD Excavator / 2017 AMD Zen + # -DGGML_FMA (FMA3) -- 2013 Intel Haswell & 2012 AMD Piledriver # Note: the following seem to yield slower results than AVX2 - ymmv - # -DLLAMA_AVX512 -- 2017 Intel Skylake and High End DeskTop (HEDT) - # -DLLAMA_AVX512_VBMI -- 2018 Intel Cannon Lake - # -DLLAMA_AVX512_VNNI -- 2021 Intel Alder Lake + # -DGGML_AVX512 -- 2017 Intel Skylake and High End DeskTop (HEDT) + # -DGGML_AVX512_VBMI -- 2018 Intel Cannon Lake + # -DGGML_AVX512_VNNI -- 2021 Intel Alder Lake - COMMON_CPU_DEFS="-DCMAKE_POSITION_INDEPENDENT_CODE=on -DLLAMA_NATIVE=off -DLLAMA_OPENMP=off" + COMMON_CPU_DEFS="-DCMAKE_POSITION_INDEPENDENT_CODE=on -DGGML_NATIVE=off -DGGML_OPENMP=off" if [ -z "${OLLAMA_CPU_TARGET}" -o "${OLLAMA_CPU_TARGET}" = "cpu" ]; then # # CPU first for the default library, set up as lowest common denominator for maximum compatibility (including Rosetta) # init_vars - CMAKE_DEFS="${COMMON_CPU_DEFS} -DLLAMA_AVX=off -DLLAMA_AVX2=off -DLLAMA_AVX512=off -DLLAMA_FMA=off -DLLAMA_F16C=off ${CMAKE_DEFS}" + CMAKE_DEFS="${COMMON_CPU_DEFS} -DGGML_AVX=off -DGGML_AVX2=off -DGGML_AVX512=off -DGGML_FMA=off -DGGML_F16C=off ${CMAKE_DEFS}" BUILD_DIR="../build/linux/${ARCH}/cpu" echo "Building LCD CPU" build @@ -116,7 +116,7 @@ if [ -z "${OLLAMA_SKIP_CPU_GENERATE}" ]; then # Approximately 400% faster than LCD on same CPU # init_vars - CMAKE_DEFS="${COMMON_CPU_DEFS} -DLLAMA_AVX=on -DLLAMA_AVX2=off -DLLAMA_AVX512=off -DLLAMA_FMA=off -DLLAMA_F16C=off ${CMAKE_DEFS}" + CMAKE_DEFS="${COMMON_CPU_DEFS} -DGGML_AVX=on -DGGML_AVX2=off -DGGML_AVX512=off -DGGML_FMA=off -DGGML_F16C=off ${CMAKE_DEFS}" BUILD_DIR="../build/linux/${ARCH}/cpu_avx" echo "Building AVX CPU" build @@ -129,7 +129,7 @@ if [ -z "${OLLAMA_SKIP_CPU_GENERATE}" ]; then # Approximately 10% faster than AVX on same CPU # init_vars - CMAKE_DEFS="${COMMON_CPU_DEFS} -DLLAMA_AVX=on -DLLAMA_AVX2=on -DLLAMA_AVX512=off -DLLAMA_FMA=on -DLLAMA_F16C=on ${CMAKE_DEFS}" + CMAKE_DEFS="${COMMON_CPU_DEFS} -DGGML_AVX=on -DGGML_AVX2=on -DGGML_AVX512=off -DGGML_FMA=on -DGGML_F16C=on ${CMAKE_DEFS}" BUILD_DIR="../build/linux/${ARCH}/cpu_avx2" echo "Building AVX2 CPU" build @@ -170,15 +170,15 @@ if [ -z "${OLLAMA_SKIP_CUDA_GENERATE}" -a -d "${CUDA_LIB_DIR}" ]; then # # CUDA compute < 6.0 lacks proper FP16 support on ARM. # Disabling has minimal performance effect while maintaining compatibility. - ARM64_DEFS="-DLLAMA_AVX=off -DLLAMA_AVX2=off -DLLAMA_AVX512=off -DLLAMA_CUDA_F16=off" + ARM64_DEFS="-DGGML_AVX=off -DGGML_AVX2=off -DGGML_AVX512=off -DGGML_CUDA_F16=off" fi # Users building from source can tune the exact flags we pass to cmake for configuring llama.cpp if [ -n "${OLLAMA_CUSTOM_CUDA_DEFS}" ]; then echo "OLLAMA_CUSTOM_CUDA_DEFS=\"${OLLAMA_CUSTOM_CUDA_DEFS}\"" - CMAKE_CUDA_DEFS="-DLLAMA_CUDA=on -DCMAKE_CUDA_ARCHITECTURES=${CMAKE_CUDA_ARCHITECTURES} ${OLLAMA_CUSTOM_CUDA_DEFS}" + CMAKE_CUDA_DEFS="-DGGML_CUDA=on -DCMAKE_CUDA_ARCHITECTURES=${CMAKE_CUDA_ARCHITECTURES} ${OLLAMA_CUSTOM_CUDA_DEFS}" echo "Building custom CUDA GPU" else - CMAKE_CUDA_DEFS="-DLLAMA_CUDA=on -DCMAKE_CUDA_FLAGS=-t8 -DLLAMA_CUDA_FORCE_MMQ=on -DCMAKE_CUDA_ARCHITECTURES=${CMAKE_CUDA_ARCHITECTURES}" + CMAKE_CUDA_DEFS="-DGGML_CUDA=on -DCMAKE_CUDA_FLAGS=-t8 -DGGML_CUDA_FORCE_MMQ=on -DCMAKE_CUDA_ARCHITECTURES=${CMAKE_CUDA_ARCHITECTURES} -DCMAKE_LIBRARY_PATH=/usr/local/cuda/compat" fi CMAKE_DEFS="${COMMON_CMAKE_DEFS} ${CMAKE_DEFS} ${ARM64_DEFS} ${CMAKE_CUDA_DEFS}" BUILD_DIR="../build/linux/${ARCH}/cuda${CUDA_VARIANT}" @@ -216,7 +216,7 @@ if [ -z "${OLLAMA_SKIP_ONEAPI_GENERATE}" -a -d "${ONEAPI_ROOT}" ]; then init_vars source ${ONEAPI_ROOT}/setvars.sh --force # set up environment variables for oneAPI CC=icx - CMAKE_DEFS="${COMMON_CMAKE_DEFS} ${CMAKE_DEFS} -DCMAKE_C_COMPILER=icx -DCMAKE_CXX_COMPILER=icpx -DLLAMA_SYCL=ON -DLLAMA_SYCL_F16=OFF" + CMAKE_DEFS="${COMMON_CMAKE_DEFS} ${CMAKE_DEFS} -DCMAKE_C_COMPILER=icx -DCMAKE_CXX_COMPILER=icpx -DGGML_SYCL=ON -DGGML_SYCL_F16=OFF" BUILD_DIR="../build/linux/${ARCH}/oneapi" EXTRA_LIBS="-fsycl -Wl,-rpath,${ONEAPI_ROOT}/compiler/latest/lib,-rpath,${ONEAPI_ROOT}/mkl/latest/lib,-rpath,${ONEAPI_ROOT}/tbb/latest/lib,-rpath,${ONEAPI_ROOT}/compiler/latest/opt/oclfpga/linux64/lib -lOpenCL -lmkl_core -lmkl_sycl_blas -lmkl_intel_ilp64 -lmkl_tbb_thread -ltbb" DEBUG_FLAGS="" # icx compiles with -O0 if we pass -g, so we must remove it @@ -254,7 +254,7 @@ if [ -z "${OLLAMA_SKIP_ROCM_GENERATE}" -a -d "${ROCM_PATH}" ]; then ROCM_VARIANT=_v$(ls ${ROCM_PATH}/lib/librocblas.so.*.*.????? | cut -f5 -d. || true) fi init_vars - CMAKE_DEFS="${COMMON_CMAKE_DEFS} ${CMAKE_DEFS} -DLLAMA_HIPBLAS=on -DCMAKE_C_COMPILER=$ROCM_PATH/llvm/bin/clang -DCMAKE_CXX_COMPILER=$ROCM_PATH/llvm/bin/clang++ -DAMDGPU_TARGETS=$(amdGPUs) -DGPU_TARGETS=$(amdGPUs)" + CMAKE_DEFS="${COMMON_CMAKE_DEFS} ${CMAKE_DEFS} -DGGML_HIPBLAS=on -DCMAKE_C_COMPILER=$ROCM_PATH/llvm/bin/clang -DCMAKE_CXX_COMPILER=$ROCM_PATH/llvm/bin/clang++ -DAMDGPU_TARGETS=$(amdGPUs) -DGPU_TARGETS=$(amdGPUs)" # Users building from source can tune the exact flags we pass to cmake for configuring llama.cpp if [ -n "${OLLAMA_CUSTOM_ROCM_DEFS}" ]; then echo "OLLAMA_CUSTOM_ROCM_DEFS=\"${OLLAMA_CUSTOM_ROCM_DEFS}\"" diff --git a/llm/generate/gen_windows.ps1 b/llm/generate/gen_windows.ps1 index e217a0382..5c6943502 100644 --- a/llm/generate/gen_windows.ps1 +++ b/llm/generate/gen_windows.ps1 @@ -39,8 +39,8 @@ function init_vars { } $script:cmakeDefs = @( "-DBUILD_SHARED_LIBS=on", - "-DLLAMA_NATIVE=off", - "-DLLAMA_OPENMP=off" + "-DGGML_NATIVE=off", + "-DGGML_OPENMP=off" ) $script:commonCpuDefs = @("-DCMAKE_POSITION_INDEPENDENT_CODE=on") $script:ARCH = $Env:PROCESSOR_ARCHITECTURE.ToLower() @@ -182,9 +182,9 @@ function cleanup { } -# -DLLAMA_AVX -- 2011 Intel Sandy Bridge & AMD Bulldozer -# -DLLAMA_AVX2 -- 2013 Intel Haswell & 2015 AMD Excavator / 2017 AMD Zen -# -DLLAMA_FMA (FMA3) -- 2013 Intel Haswell & 2012 AMD Piledriver +# -DGGML_AVX -- 2011 Intel Sandy Bridge & AMD Bulldozer +# -DGGML_AVX2 -- 2013 Intel Haswell & 2015 AMD Excavator / 2017 AMD Zen +# -DGGML_FMA (FMA3) -- 2013 Intel Haswell & 2012 AMD Piledriver function build_static() { @@ -204,13 +204,13 @@ function build_static() { "-DCMAKE_C_COMPILER=gcc.exe", "-DCMAKE_CXX_COMPILER=g++.exe", "-DBUILD_SHARED_LIBS=off", - "-DLLAMA_NATIVE=off", - "-DLLAMA_AVX=off", - "-DLLAMA_AVX2=off", - "-DLLAMA_AVX512=off", - "-DLLAMA_F16C=off", - "-DLLAMA_FMA=off", - "-DLLAMA_OPENMP=off") + "-DGGML_NATIVE=off", + "-DGGML_AVX=off", + "-DGGML_AVX2=off", + "-DGGML_AVX512=off", + "-DGGML_F16C=off", + "-DGGML_FMA=off", + "-DGGML_OPENMP=off") $script:buildDir="../build/windows/${script:ARCH}_static" write-host "Building static library" build @@ -224,7 +224,7 @@ function build_cpu($gen_arch) { if ((-not "${env:OLLAMA_SKIP_CPU_GENERATE}" ) -and ((-not "${env:OLLAMA_CPU_TARGET}") -or ("${env:OLLAMA_CPU_TARGET}" -eq "cpu"))) { # remaining llama.cpp builds use MSVC init_vars - $script:cmakeDefs = $script:commonCpuDefs + @("-A", $gen_arch, "-DLLAMA_AVX=off", "-DLLAMA_AVX2=off", "-DLLAMA_AVX512=off", "-DLLAMA_FMA=off", "-DLLAMA_F16C=off") + $script:cmakeDefs + $script:cmakeDefs = $script:commonCpuDefs + @("-A", $gen_arch, "-DGGML_AVX=off", "-DGGML_AVX2=off", "-DGGML_AVX512=off", "-DGGML_FMA=off", "-DGGML_F16C=off") + $script:cmakeDefs $script:buildDir="../build/windows/${script:ARCH}/cpu" $script:distDir="$script:DIST_BASE\cpu" write-host "Building LCD CPU" @@ -239,7 +239,7 @@ function build_cpu($gen_arch) { function build_cpu_avx() { if ((-not "${env:OLLAMA_SKIP_CPU_GENERATE}" ) -and ((-not "${env:OLLAMA_CPU_TARGET}") -or ("${env:OLLAMA_CPU_TARGET}" -eq "cpu_avx"))) { init_vars - $script:cmakeDefs = $script:commonCpuDefs + @("-A", "x64", "-DLLAMA_AVX=on", "-DLLAMA_AVX2=off", "-DLLAMA_AVX512=off", "-DLLAMA_FMA=off", "-DLLAMA_F16C=off") + $script:cmakeDefs + $script:cmakeDefs = $script:commonCpuDefs + @("-A", "x64", "-DGGML_AVX=on", "-DGGML_AVX2=off", "-DGGML_AVX512=off", "-DGGML_FMA=off", "-DGGML_F16C=off") + $script:cmakeDefs $script:buildDir="../build/windows/${script:ARCH}/cpu_avx" $script:distDir="$script:DIST_BASE\cpu_avx" write-host "Building AVX CPU" @@ -254,7 +254,7 @@ function build_cpu_avx() { function build_cpu_avx2() { if ((-not "${env:OLLAMA_SKIP_CPU_GENERATE}" ) -and ((-not "${env:OLLAMA_CPU_TARGET}") -or ("${env:OLLAMA_CPU_TARGET}" -eq "cpu_avx2"))) { init_vars - $script:cmakeDefs = $script:commonCpuDefs + @("-A", "x64", "-DLLAMA_AVX=on", "-DLLAMA_AVX2=on", "-DLLAMA_AVX512=off", "-DLLAMA_FMA=on", "-DLLAMA_F16C=on") + $script:cmakeDefs + $script:cmakeDefs = $script:commonCpuDefs + @("-A", "x64", "-DGGML_AVX=on", "-DGGML_AVX2=on", "-DGGML_AVX512=off", "-DGGML_FMA=on", "-DGGML_F16C=on") + $script:cmakeDefs $script:buildDir="../build/windows/${script:ARCH}/cpu_avx2" $script:distDir="$script:DIST_BASE\cpu_avx2" write-host "Building AVX2 CPU" @@ -279,9 +279,9 @@ function build_cuda() { $script:distDir="$script:DIST_BASE\cuda$script:CUDA_VARIANT" $script:cmakeDefs += @( "-A", "x64", - "-DLLAMA_CUDA=ON", - "-DLLAMA_AVX=on", - "-DLLAMA_AVX2=off", + "-DGGML_CUDA=ON", + "-DGGML_AVX=on", + "-DGGML_AVX2=off", "-DCUDAToolkit_INCLUDE_DIR=$script:CUDA_INCLUDE_DIR", "-DCMAKE_CUDA_FLAGS=-t8", "-DCMAKE_CUDA_ARCHITECTURES=${script:CMAKE_CUDA_ARCHITECTURES}" @@ -319,7 +319,7 @@ function build_oneapi() { $script:distDir ="$script:DIST_BASE\oneapi$script:ONEAPI_VARIANT" $script:cmakeDefs += @( "-G", "MinGW Makefiles", - "-DLLAMA_SYCL=ON", + "-DGGML_SYCL=ON", "-DCMAKE_C_COMPILER=icx", "-DCMAKE_CXX_COMPILER=icx", "-DCMAKE_BUILD_TYPE=Release" @@ -365,10 +365,10 @@ function build_rocm() { "-G", "Ninja", "-DCMAKE_C_COMPILER=clang.exe", "-DCMAKE_CXX_COMPILER=clang++.exe", - "-DLLAMA_HIPBLAS=on", + "-DGGML_HIPBLAS=on", "-DHIP_PLATFORM=amd", - "-DLLAMA_AVX=on", - "-DLLAMA_AVX2=off", + "-DGGML_AVX=on", + "-DGGML_AVX2=off", "-DCMAKE_POSITION_INDEPENDENT_CODE=on", "-DAMDGPU_TARGETS=$(amdGPUs)", "-DGPU_TARGETS=$(amdGPUs)" diff --git a/llm/llama.cpp b/llm/llama.cpp index 7c26775ad..d7fd29fff 160000 --- a/llm/llama.cpp +++ b/llm/llama.cpp @@ -1 +1 @@ -Subproject commit 7c26775adb579e92b59c82e8084c07a1d0f75e9c +Subproject commit d7fd29fff16456ce9c3a23fd2d09a66256b05aff diff --git a/llm/llm.go b/llm/llm.go index 2a0c4b91a..157176246 100644 --- a/llm/llm.go +++ b/llm/llm.go @@ -1,12 +1,12 @@ package llm -// #cgo CFLAGS: -Illama.cpp -// #cgo darwin,arm64 LDFLAGS: ${SRCDIR}/build/darwin/arm64_static/libllama.a -lstdc++ -// #cgo darwin,amd64 LDFLAGS: ${SRCDIR}/build/darwin/x86_64_static/libllama.a -lstdc++ -// #cgo windows,amd64 LDFLAGS: ${SRCDIR}/build/windows/amd64_static/libllama.a -static -lstdc++ -// #cgo windows,arm64 LDFLAGS: ${SRCDIR}/build/windows/arm64_static/libllama.a -static -lstdc++ -// #cgo linux,amd64 LDFLAGS: ${SRCDIR}/build/linux/x86_64_static/libllama.a -lstdc++ -// #cgo linux,arm64 LDFLAGS: ${SRCDIR}/build/linux/arm64_static/libllama.a -lstdc++ +// #cgo CFLAGS: -Illama.cpp/include -Illama.cpp/ggml/include +// #cgo darwin,arm64 LDFLAGS: ${SRCDIR}/build/darwin/arm64_static/src/libllama.a ${SRCDIR}/build/darwin/arm64_static/ggml/src/libggml.a -lstdc++ -framework Accelerate -framework Metal +// #cgo darwin,amd64 LDFLAGS: ${SRCDIR}/build/darwin/x86_64_static/src/libllama.a ${SRCDIR}/build/darwin/x86_64_static/ggml/src/libggml.a -lstdc++ -framework Accelerate -framework Metal +// #cgo windows,amd64 LDFLAGS: ${SRCDIR}/build/windows/amd64_static/src/libllama.a ${SRCDIR}/build/windows/amd64_static/ggml/src/libggml.a -static -lstdc++ +// #cgo windows,arm64 LDFLAGS: ${SRCDIR}/build/windows/arm64_static/src/libllama.a ${SRCDIR}/build/windows/arm64_static/ggml/src/libggml.a -static -lstdc++ +// #cgo linux,amd64 LDFLAGS: ${SRCDIR}/build/linux/x86_64_static/src/libllama.a ${SRCDIR}/build/linux/x86_64_static/ggml/src/libggml.a -lstdc++ +// #cgo linux,arm64 LDFLAGS: ${SRCDIR}/build/linux/arm64_static/src/libllama.a ${SRCDIR}/build/linux/arm64_static/ggml/src/libggml. -lstdc++ // #include // #include "llama.h" import "C" diff --git a/llm/patches/01-load-progress.diff b/llm/patches/01-load-progress.diff index be5286091..a053c1c2c 100644 --- a/llm/patches/01-load-progress.diff +++ b/llm/patches/01-load-progress.diff @@ -1,8 +1,8 @@ diff --git a/common/common.cpp b/common/common.cpp -index 73ff0e85..6adb1a92 100644 +index 2c05a4d4..927f0e3d 100644 --- a/common/common.cpp +++ b/common/common.cpp -@@ -2447,6 +2447,8 @@ struct llama_model_params llama_model_params_from_gpt_params(const gpt_params & +@@ -2093,6 +2093,8 @@ struct llama_model_params llama_model_params_from_gpt_params(const gpt_params & mparams.use_mmap = params.use_mmap; mparams.use_mlock = params.use_mlock; mparams.check_tensors = params.check_tensors; @@ -12,10 +12,10 @@ index 73ff0e85..6adb1a92 100644 mparams.kv_overrides = NULL; } else { diff --git a/common/common.h b/common/common.h -index 58ed72f4..0bb2605e 100644 +index 65c0ef81..ebca2c77 100644 --- a/common/common.h +++ b/common/common.h -@@ -180,6 +180,13 @@ struct gpt_params { +@@ -184,6 +184,13 @@ struct gpt_params { std::string mmproj = ""; // path to multimodal projector std::vector image; // path to image file(s) @@ -26,6 +26,6 @@ index 58ed72f4..0bb2605e 100644 + // context pointer passed to the progress callback + void * progress_callback_user_data; + - // server params - int32_t port = 8080; // server listens on this network port - int32_t timeout_read = 600; // http read timeout in seconds + // embedding + bool embedding = false; // get only sentence embedding + int32_t embd_normalize = 2; // normalisation for embendings (-1=none, 0=max absolute int16, 1=taxicab, 2=euclidean, >2=p-norm) diff --git a/llm/patches/03-load_exception.diff b/llm/patches/03-load_exception.diff index eb245c2a9..026661963 100644 --- a/llm/patches/03-load_exception.diff +++ b/llm/patches/03-load_exception.diff @@ -1,17 +1,8 @@ -From 544a2d2e646d39e878d87dfbb3398a356bc560ab Mon Sep 17 00:00:00 2001 -From: Michael Yang -Date: Thu, 23 May 2024 11:18:45 -0700 -Subject: [PATCH] throw exception on load errors - ---- - llama.cpp | 25 ++++++++++++++++--------- - 1 file changed, 16 insertions(+), 9 deletions(-) - -diff --git a/llama.cpp b/llama.cpp -index 15c66077..8ba90b6a 100644 ---- a/llama.cpp -+++ b/llama.cpp -@@ -6346,7 +6346,7 @@ static int llama_model_load(const std::string & fname, llama_model & model, llam +diff --git a/src/llama.cpp b/src/llama.cpp +index 73f52435..58a00fb1 100644 +--- a/src/llama.cpp ++++ b/src/llama.cpp +@@ -7241,7 +7241,7 @@ static int llama_model_load(const std::string & fname, llama_model & model, llam } } catch (const std::exception & err) { LLAMA_LOG_ERROR("%s: error loading model: %s\n", __func__, err.what()); @@ -20,7 +11,7 @@ index 15c66077..8ba90b6a 100644 } return 0; -@@ -15600,16 +15600,23 @@ struct llama_model * llama_load_model_from_file( +@@ -17564,16 +17564,23 @@ struct llama_model * llama_load_model_from_file( } model->rpc_servers.push_back(servers); } @@ -52,6 +43,3 @@ index 15c66077..8ba90b6a 100644 } return model; --- -2.45.1 - diff --git a/llm/patches/04-metal.diff b/llm/patches/04-metal.diff index f8fa7db76..e63732e70 100644 --- a/llm/patches/04-metal.diff +++ b/llm/patches/04-metal.diff @@ -1,7 +1,7 @@ -diff --git a/ggml-metal.m b/ggml-metal.m +diff --git a/ggml/src/ggml-metal.m b/ggml/src/ggml-metal.m index 0207b787..b5e9884b 100644 ---- a/ggml-metal.m -+++ b/ggml-metal.m +--- a/ggml/src/ggml-metal.m ++++ b/ggml/src/ggml-metal.m @@ -1396,27 +1396,23 @@ static enum ggml_status ggml_metal_graph_compute( // to the matrix-vector kernel int ne11_mm_min = 1; diff --git a/llm/patches/05-default-pretokenizer.diff b/llm/patches/05-default-pretokenizer.diff index 2a2e7306e..f4eaced72 100644 --- a/llm/patches/05-default-pretokenizer.diff +++ b/llm/patches/05-default-pretokenizer.diff @@ -1,8 +1,8 @@ -diff --git a/llama.cpp b/llama.cpp -index 61948751..4b72a293 100644 ---- a/llama.cpp -+++ b/llama.cpp -@@ -4824,16 +4824,7 @@ static void llm_load_vocab( +diff --git a/src/llama.cpp b/src/llama.cpp +index 73f52435..2b81b4bd 100644 +--- a/src/llama.cpp ++++ b/src/llama.cpp +@@ -5092,16 +5092,7 @@ static void llm_load_vocab( // for now, only BPE models have pre-tokenizers if (vocab.type == LLAMA_VOCAB_TYPE_BPE) { @@ -20,13 +20,13 @@ index 61948751..4b72a293 100644 vocab.type_pre = LLAMA_VOCAB_PRE_TYPE_DEFAULT; } else if ( tokenizer_pre == "llama3" || -@@ -4888,7 +4879,8 @@ static void llm_load_vocab( - tokenizer_pre == "poro-chat") { - vocab.type_pre = LLAMA_VOCAB_PRE_TYPE_PORO; +@@ -5164,7 +5155,8 @@ static void llm_load_vocab( + tokenizer_pre == "jais") { + vocab.type_pre = LLAMA_VOCAB_PRE_TYPE_JAIS; } else { - throw std::runtime_error(format("unknown pre-tokenizer type: '%s'", tokenizer_pre.c_str())); + LLAMA_LOG_WARN("%s: missing or unrecognized pre-tokenizer type, using: 'default'\n", __func__); + vocab.type_pre = LLAMA_VOCAB_PRE_TYPE_DEFAULT; } - } else { + } else if (vocab.type == LLAMA_VOCAB_TYPE_SPM) { vocab.type_pre = LLAMA_VOCAB_PRE_TYPE_DEFAULT; diff --git a/llm/patches/06-qwen2.diff b/llm/patches/06-qwen2.diff index d7b0c1555..1c7109f6f 100644 --- a/llm/patches/06-qwen2.diff +++ b/llm/patches/06-qwen2.diff @@ -1,7 +1,7 @@ -diff --git a/llama.cpp b/llama.cpp +diff --git a/src/llama.cpp b/src/llama.cpp index 40d2ec2c..f34eb79a 100644 ---- a/llama.cpp -+++ b/llama.cpp +--- a/src/llama.cpp ++++ b/src/llama.cpp @@ -6943,7 +6943,7 @@ static struct ggml_tensor * llm_build_kqv( struct ggml_tensor * kq = ggml_mul_mat(ctx, k, q); cb(kq, "kq", il); diff --git a/llm/patches/07-embeddings.diff b/llm/patches/07-embeddings.diff new file mode 100644 index 000000000..a84e3b06c --- /dev/null +++ b/llm/patches/07-embeddings.diff @@ -0,0 +1,45 @@ +diff --git a/src/llama.cpp b/src/llama.cpp +index 1fe2b9f7..a43312a7 100644 +--- a/src/llama.cpp ++++ b/src/llama.cpp +@@ -13689,7 +13689,7 @@ static size_t llama_output_reserve(llama_context & lctx, size_t n_outputs) { + const auto n_embd = hparams.n_embd; + + // TODO: use a per-batch flag for logits presence instead +- const bool has_logits = !cparams.embeddings; ++ const bool has_logits = cparams.causal_attn; + const bool has_embd = lctx.is_encoding || (cparams.embeddings && (cparams.pooling_type == LLAMA_POOLING_TYPE_NONE)); + + const size_t logits_size = has_logits ? n_vocab*n_outputs_max : 0; +@@ -13959,17 +13959,25 @@ static int llama_decode_internal( + // no output + res = nullptr; + embd = nullptr; +- } else if (cparams.embeddings) { +- res = nullptr; // do not extract logits for embedding case +- embd = gf->nodes[gf->n_nodes - 1]; +- if (strcmp(embd->name, "result_embd_pooled") != 0) { +- embd = gf->nodes[gf->n_nodes - 2]; ++ } ++ ++ if (cparams.embeddings) { ++ for (int i = gf->n_nodes - 1; i >= 0; --i) { ++ embd = gf->nodes[i]; ++ if (strcmp(embd->name, "result_embd_pooled") == 0) { ++ break; ++ } + } + GGML_ASSERT(strcmp(embd->name, "result_embd_pooled") == 0 && "missing embeddings tensor"); +- } else { ++ } else { + embd = nullptr; // do not extract embeddings when not needed + GGML_ASSERT(strcmp(res->name, "result_output") == 0 && "missing result_output tensor"); + } ++ ++ if (!cparams.causal_attn) { ++ res = nullptr; // do not extract logits when not needed ++ } ++ + // LLAMA_LOG_INFO("graph build time: %.3f ms (%d nodes, %d leafs)\n", (ggml_time_us() - t_start_us)/1000.0, gf->n_nodes, gf->n_leafs); + + ggml_backend_sched_alloc_graph(lctx.sched, gf); diff --git a/llm/patches/07-gemma.diff b/llm/patches/07-gemma.diff deleted file mode 100644 index 86eac3d17..000000000 --- a/llm/patches/07-gemma.diff +++ /dev/null @@ -1,305 +0,0 @@ -From 5cadb45f39d001ffbad95b690d6cf0abcb4a6d96 Mon Sep 17 00:00:00 2001 -From: Ollama maintainers -Date: Wed, 26 Jun 2024 16:18:09 -0700 -Subject: [PATCH] Architecture support - ---- - llama.cpp | 194 +++++++++++++++++++++++++++++++++++++++++++++++++++++- - 1 file changed, 193 insertions(+), 1 deletion(-) - -diff --git a/llama.cpp b/llama.cpp -index 61948751..3b4196f5 100644 ---- a/llama.cpp -+++ b/llama.cpp -@@ -217,6 +217,7 @@ enum llm_arch { - LLM_ARCH_INTERNLM2, - LLM_ARCH_MINICPM, - LLM_ARCH_GEMMA, -+ LLM_ARCH_GEMMA2, - LLM_ARCH_STARCODER2, - LLM_ARCH_MAMBA, - LLM_ARCH_XVERSE, -@@ -255,6 +256,7 @@ static const std::map LLM_ARCH_NAMES = { - { LLM_ARCH_INTERNLM2, "internlm2" }, - { LLM_ARCH_MINICPM, "minicpm" }, - { LLM_ARCH_GEMMA, "gemma" }, -+ { LLM_ARCH_GEMMA2, "gemma2" }, - { LLM_ARCH_STARCODER2, "starcoder2" }, - { LLM_ARCH_MAMBA, "mamba" }, - { LLM_ARCH_XVERSE, "xverse" }, -@@ -464,10 +466,12 @@ enum llm_tensor { - LLM_TENSOR_ATTN_NORM, - LLM_TENSOR_ATTN_NORM_2, - LLM_TENSOR_ATTN_OUT_NORM, -+ LLM_TENSOR_ATTN_POST_NORM, - LLM_TENSOR_ATTN_ROT_EMBD, - LLM_TENSOR_FFN_GATE_INP, - LLM_TENSOR_FFN_GATE_INP_SHEXP, - LLM_TENSOR_FFN_NORM, -+ LLM_TENSOR_FFN_POST_NORM, - LLM_TENSOR_FFN_GATE, - LLM_TENSOR_FFN_DOWN, - LLM_TENSOR_FFN_UP, -@@ -960,6 +964,24 @@ static const std::map> LLM_TENSOR_NA - { LLM_TENSOR_FFN_UP, "blk.%d.ffn_up" }, - }, - }, -+ { -+ LLM_ARCH_GEMMA2, -+ { -+ { LLM_TENSOR_TOKEN_EMBD, "token_embd" }, -+ { LLM_TENSOR_OUTPUT_NORM, "output_norm" }, -+ { LLM_TENSOR_ATTN_NORM, "blk.%d.attn_norm" }, -+ { LLM_TENSOR_ATTN_Q, "blk.%d.attn_q" }, -+ { LLM_TENSOR_ATTN_K, "blk.%d.attn_k" }, -+ { LLM_TENSOR_ATTN_V, "blk.%d.attn_v" }, -+ { LLM_TENSOR_ATTN_OUT, "blk.%d.attn_output" }, -+ { LLM_TENSOR_ATTN_POST_NORM, "blk.%d.post_attention_norm" }, -+ { LLM_TENSOR_FFN_NORM, "blk.%d.ffn_norm" }, -+ { LLM_TENSOR_FFN_GATE, "blk.%d.ffn_gate" }, -+ { LLM_TENSOR_FFN_DOWN, "blk.%d.ffn_down" }, -+ { LLM_TENSOR_FFN_UP, "blk.%d.ffn_up" }, -+ { LLM_TENSOR_FFN_POST_NORM, "blk.%d.post_ffw_norm" }, -+ }, -+ }, - { - LLM_ARCH_STARCODER2, - { -@@ -1941,6 +1963,8 @@ enum e_model { - MODEL_8x22B, - MODEL_16x12B, - MODEL_10B_128x3_66B, -+ MODEL_9B, -+ MODEL_27B, - }; - - static const size_t kiB = 1024; -@@ -2114,6 +2138,7 @@ struct llama_layer { - struct ggml_tensor * attn_out_norm_b; - struct ggml_tensor * attn_q_a_norm; - struct ggml_tensor * attn_kv_a_norm; -+ struct ggml_tensor * attn_post_norm; - - // attention - struct ggml_tensor * wq; -@@ -2136,6 +2161,7 @@ struct llama_layer { - // normalization - struct ggml_tensor * ffn_norm; - struct ggml_tensor * ffn_norm_b; -+ struct ggml_tensor * ffn_post_norm; - struct ggml_tensor * layer_out_norm; - struct ggml_tensor * layer_out_norm_b; - struct ggml_tensor * ffn_norm_exps; -@@ -4529,6 +4555,16 @@ static void llm_load_hparams( - } - } break; - case LLM_ARCH_GEMMA: -+ { -+ ml.get_key(LLM_KV_ATTENTION_LAYERNORM_RMS_EPS, hparams.f_norm_rms_eps); -+ -+ switch (hparams.n_layer) { -+ case 18: model.type = e_model::MODEL_9B; break; -+ case 28: model.type = e_model::MODEL_27B; break; -+ default: model.type = e_model::MODEL_UNKNOWN; -+ } -+ } break; -+ case LLM_ARCH_GEMMA2: - { - ml.get_key(LLM_KV_ATTENTION_LAYERNORM_RMS_EPS, hparams.f_norm_rms_eps); - -@@ -6305,6 +6341,40 @@ static bool llm_load_tensors( - layer.ffn_down = ml.create_tensor(ctx_split, tn(LLM_TENSOR_FFN_DOWN, "weight", i), { n_ff, n_embd}); - } - } break; -+ case LLM_ARCH_GEMMA2: -+ { -+ model.tok_embd = ml.create_tensor(ctx_input, tn(LLM_TENSOR_TOKEN_EMBD, "weight"), {n_embd, n_vocab}); -+ -+ // output -+ model.output_norm = ml.create_tensor(ctx_output, tn(LLM_TENSOR_OUTPUT_NORM, "weight"), {n_embd}); -+ model.output = ml.create_tensor(ctx_output, tn(LLM_TENSOR_TOKEN_EMBD, "weight"), {n_embd, n_vocab}, llama_model_loader::TENSOR_DUPLICATED); // same as tok_embd, duplicated to allow offloading -+ -+ const int64_t n_ff = hparams.n_ff; -+ const int64_t n_embd_head_k = hparams.n_embd_head_k; -+ const int64_t n_embd_k_gqa = hparams.n_embd_k_gqa(); -+ const int64_t n_embd_v_gqa = hparams.n_embd_v_gqa(); -+ -+ for (uint32_t i = 0; i < n_layer; ++i) { -+ ggml_context * ctx_layer = ctx_for_layer(i); -+ ggml_context * ctx_split = ctx_for_layer_split(i); -+ -+ auto & layer = model.layers[i]; -+ -+ layer.attn_norm = ml.create_tensor(ctx_layer, tn(LLM_TENSOR_ATTN_NORM, "weight", i), {n_embd}); -+ -+ layer.wq = ml.create_tensor(ctx_split, tn(LLM_TENSOR_ATTN_Q, "weight", i), {n_embd, n_embd_head_k * hparams.n_head}); -+ layer.wk = ml.create_tensor(ctx_split, tn(LLM_TENSOR_ATTN_K, "weight", i), {n_embd, n_embd_k_gqa}); -+ layer.wv = ml.create_tensor(ctx_split, tn(LLM_TENSOR_ATTN_V, "weight", i), {n_embd, n_embd_v_gqa}); -+ layer.wo = ml.create_tensor(ctx_split, tn(LLM_TENSOR_ATTN_OUT, "weight", i), {n_embd_head_k * hparams.n_head, n_embd}); -+ layer.attn_post_norm = ml.create_tensor(ctx_split, tn(LLM_TENSOR_ATTN_POST_NORM, "weight", i), {n_embd}); -+ -+ layer.ffn_norm = ml.create_tensor(ctx_layer, tn(LLM_TENSOR_FFN_NORM, "weight", i), {n_embd}); -+ layer.ffn_gate = ml.create_tensor(ctx_split, tn(LLM_TENSOR_FFN_GATE, "weight", i), {n_embd, n_ff}); -+ layer.ffn_up = ml.create_tensor(ctx_split, tn(LLM_TENSOR_FFN_UP, "weight", i), {n_embd, n_ff}); -+ layer.ffn_down = ml.create_tensor(ctx_split, tn(LLM_TENSOR_FFN_DOWN, "weight", i), { n_ff, n_embd}); -+ layer.ffn_post_norm = ml.create_tensor(ctx_layer, tn(LLM_TENSOR_FFN_POST_NORM, "weight", i), {n_embd}); -+ } -+ } break; - case LLM_ARCH_STARCODER2: - { - model.tok_embd = ml.create_tensor(ctx_input, tn(LLM_TENSOR_TOKEN_EMBD, "weight"), {n_embd, n_vocab}); -@@ -10614,6 +10684,123 @@ struct llm_build_context { - return gf; - } - -+ struct ggml_cgraph * build_gemma2() { -+ struct ggml_cgraph * gf = ggml_new_graph_custom(ctx0, LLAMA_MAX_NODES, false); -+ -+ const int64_t n_embd_head_k = hparams.n_embd_head_k; -+ -+ struct ggml_tensor * cur; -+ struct ggml_tensor * inpL; -+ -+ inpL = llm_build_inp_embd(ctx0, lctx, hparams, batch, model.tok_embd, cb); -+ -+ inpL = ggml_scale(ctx0, inpL, sqrtf(n_embd)); -+ cb(inpL, "inp_scaled", -1); -+ -+ // inp_pos - contains the positions -+ struct ggml_tensor * inp_pos = build_inp_pos(); -+ -+ // KQ_mask (mask for 1 head, it will be broadcasted to all heads) -+ struct ggml_tensor * KQ_mask = build_inp_KQ_mask(); -+ -+ for (int il = 0; il < n_layer; ++il) { -+ // norm -+ cur = llm_build_norm(ctx0, inpL, hparams, -+ model.layers[il].attn_norm, NULL, -+ LLM_NORM_RMS, cb, il); -+ cb(cur, "attn_norm", il); -+ -+ // self-attention -+ { -+ // compute Q and K and RoPE them -+ struct ggml_tensor * Qcur = ggml_mul_mat(ctx0, model.layers[il].wq, cur); -+ cb(Qcur, "Qcur", il); -+ -+ struct ggml_tensor * Kcur = ggml_mul_mat(ctx0, model.layers[il].wk, cur); -+ cb(Kcur, "Kcur", il); -+ -+ struct ggml_tensor * Vcur = ggml_mul_mat(ctx0, model.layers[il].wv, cur); -+ cb(Vcur, "Vcur", il); -+ -+ Qcur = ggml_rope_ext( -+ ctx0, ggml_reshape_3d(ctx0, Qcur, n_embd_head_k, n_head, n_tokens), inp_pos, nullptr, -+ n_embd_head_k, rope_type, n_ctx_orig, freq_base, freq_scale, -+ ext_factor, attn_factor, beta_fast, beta_slow); -+ cb(Qcur, "Qcur", il); -+ -+ Qcur = ggml_scale(ctx0, Qcur, 1.0f / sqrtf(float(n_embd_head_k))); -+ cb(Qcur, "Qcur_scaled", il); -+ -+ Kcur = ggml_rope_ext( -+ ctx0, ggml_reshape_3d(ctx0, Kcur, n_embd_head_k, n_head_kv, n_tokens), inp_pos, nullptr, -+ n_embd_head_k, rope_type, n_ctx_orig, freq_base, freq_scale, -+ ext_factor, attn_factor, beta_fast, beta_slow); -+ cb(Kcur, "Kcur", il); -+ -+ cur = llm_build_kv(ctx0, model, hparams, cparams, kv_self, gf, -+ model.layers[il].wo, NULL, -+ Kcur, Vcur, Qcur, KQ_mask, n_tokens, kv_head, n_kv, 1.0f, cb, il); -+ } -+ -+ if (il == n_layer - 1) { -+ // skip computing output for unused tokens -+ struct ggml_tensor * inp_out_ids = build_inp_out_ids(); -+ cur = ggml_get_rows(ctx0, cur, inp_out_ids); -+ inpL = ggml_get_rows(ctx0, inpL, inp_out_ids); -+ } -+ -+ cur = llm_build_norm(ctx0, cur, hparams, -+ model.layers[il].attn_post_norm, NULL, -+ LLM_NORM_RMS, cb, il); -+ cb(cur, "attn_post_norm", il); -+ -+ struct ggml_tensor * sa_out = ggml_add(ctx0, cur, inpL); -+ cb(sa_out, "sa_out", il); -+ -+ cur = llm_build_norm(ctx0, sa_out, hparams, -+ model.layers[il].ffn_norm, NULL, -+ LLM_NORM_RMS, cb, il); -+ cb(cur, "ffn_norm", il); -+ -+ // feed-forward network -+ { -+ cur = llm_build_ffn(ctx0, cur, -+ model.layers[il].ffn_up, NULL, -+ model.layers[il].ffn_gate, NULL, -+ model.layers[il].ffn_down, NULL, -+ NULL, -+ LLM_FFN_GELU, LLM_FFN_PAR, cb, il); -+ cb(cur, "ffn_out", il); -+ } -+ -+ cur = llm_build_norm(ctx0, cur, hparams, -+ model.layers[il].ffn_post_norm, NULL, -+ LLM_NORM_RMS, cb, -1); -+ cb(cur, "ffn_post_norm", -1); -+ -+ cur = ggml_add(ctx0, cur, sa_out); -+ cb(cur, "l_out", il); -+ -+ // input for next layer -+ inpL = cur; -+ } -+ -+ cur = inpL; -+ -+ cur = llm_build_norm(ctx0, cur, hparams, -+ model.output_norm, NULL, -+ LLM_NORM_RMS, cb, -1); -+ cb(cur, "result_norm", -1); -+ -+ // lm_head -+ cur = ggml_mul_mat(ctx0, model.output, cur); -+ cb(cur, "result_output", -1); -+ -+ ggml_build_forward_expand(gf, cur); -+ -+ return gf; -+ } -+ - struct ggml_cgraph * build_starcoder2() { - struct ggml_cgraph * gf = ggml_new_graph_custom(ctx0, LLAMA_MAX_NODES, false); - -@@ -11847,6 +12034,10 @@ static struct ggml_cgraph * llama_build_graph( - { - result = llm.build_gemma(); - } break; -+ case LLM_ARCH_GEMMA2: -+ { -+ result = llm.build_gemma2(); -+ } break; - case LLM_ARCH_STARCODER2: - { - result = llm.build_starcoder2(); -@@ -16671,6 +16862,7 @@ enum llama_rope_type llama_rope_type(const struct llama_model * model) { - case LLM_ARCH_PHI2: - case LLM_ARCH_PHI3: - case LLM_ARCH_GEMMA: -+ case LLM_ARCH_GEMMA2: - case LLM_ARCH_STARCODER2: - case LLM_ARCH_GPTNEOX: - return LLAMA_ROPE_TYPE_NEOX; -@@ -18551,7 +18743,7 @@ static int32_t llama_chat_apply_template_internal( - if (add_ass) { - ss << "assistant\n"; - } -- } else if (tmpl == "gemma" || tmpl.find("") != std::string::npos) { -+ } else if (tmpl == "gemma" || tmpl == "gemma2" || tmpl.find("") != std::string::npos) { - // google/gemma-7b-it - std::string system_prompt = ""; - for (auto message : chat) { --- -2.45.2 - diff --git a/llm/patches/09-pooling.diff b/llm/patches/09-pooling.diff index 348fbfdc4..2e4fe11ee 100644 --- a/llm/patches/09-pooling.diff +++ b/llm/patches/09-pooling.diff @@ -1,8 +1,8 @@ -diff --git a/llama.cpp b/llama.cpp -index 61948751..61fe7b57 100644 ---- a/llama.cpp -+++ b/llama.cpp -@@ -7591,14 +7591,14 @@ struct llm_build_context { +diff --git a/src/llama.cpp b/src/llama.cpp +index 721b8f4e..cfe7ac40 100644 +--- a/src/llama.cpp ++++ b/src/llama.cpp +@@ -8420,14 +8420,14 @@ struct llm_build_context { } struct ggml_tensor * build_inp_mean() { @@ -19,7 +19,7 @@ index 61948751..61fe7b57 100644 cb(lctx.inp_cls, "inp_cls", -1); ggml_set_input(lctx.inp_cls); return lctx.inp_cls; -@@ -12062,19 +12062,16 @@ static void llama_set_inputs(llama_context & lctx, const llama_batch & batch) { +@@ -13847,19 +13847,16 @@ static void llama_set_inputs(llama_context & lctx, const llama_batch & batch) { GGML_ASSERT(ggml_backend_buffer_is_host(lctx.inp_mean->buffer)); float * data = (float *) lctx.inp_mean->data; @@ -42,7 +42,7 @@ index 61948751..61fe7b57 100644 const uint64_t s = sum[i]; if (s > 0) { div[i] = 1.0f/float(s); -@@ -12094,14 +12091,11 @@ static void llama_set_inputs(llama_context & lctx, const llama_batch & batch) { +@@ -13879,14 +13876,11 @@ static void llama_set_inputs(llama_context & lctx, const llama_batch & batch) { GGML_ASSERT(ggml_backend_buffer_is_host(lctx.inp_cls->buffer)); uint32_t * data = (uint32_t *) lctx.inp_cls->data; From 78fb33dd07ecbbd78de2293bc542187afa6b671b Mon Sep 17 00:00:00 2001 From: Jeffrey Morgan Date: Fri, 5 Jul 2024 15:18:36 -0400 Subject: [PATCH 026/384] fix typo in cgo directives in `llm.go` (#5501) --- llm/llm.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/llm/llm.go b/llm/llm.go index 157176246..fb6d4b5c7 100644 --- a/llm/llm.go +++ b/llm/llm.go @@ -6,7 +6,7 @@ package llm // #cgo windows,amd64 LDFLAGS: ${SRCDIR}/build/windows/amd64_static/src/libllama.a ${SRCDIR}/build/windows/amd64_static/ggml/src/libggml.a -static -lstdc++ // #cgo windows,arm64 LDFLAGS: ${SRCDIR}/build/windows/arm64_static/src/libllama.a ${SRCDIR}/build/windows/arm64_static/ggml/src/libggml.a -static -lstdc++ // #cgo linux,amd64 LDFLAGS: ${SRCDIR}/build/linux/x86_64_static/src/libllama.a ${SRCDIR}/build/linux/x86_64_static/ggml/src/libggml.a -lstdc++ -// #cgo linux,arm64 LDFLAGS: ${SRCDIR}/build/linux/arm64_static/src/libllama.a ${SRCDIR}/build/linux/arm64_static/ggml/src/libggml. -lstdc++ +// #cgo linux,arm64 LDFLAGS: ${SRCDIR}/build/linux/arm64_static/src/libllama.a ${SRCDIR}/build/linux/arm64_static/ggml/src/libggml.a -lstdc++ // #include // #include "llama.h" import "C" From 269ed6e6a2cea822ab137d40d5c70c8bf09470f8 Mon Sep 17 00:00:00 2001 From: Michael Yang Date: Mon, 17 Jun 2024 10:38:55 -0700 Subject: [PATCH 027/384] update message processing --- server/images.go | 17 +- server/prompt.go | 241 ++++-------------- server/prompt_test.go | 317 ++++++++++++------------ server/routes.go | 508 ++++++++++++-------------------------- template/template.go | 169 ++++++++++++- template/template_test.go | 153 +++++++++++- 6 files changed, 685 insertions(+), 720 deletions(-) diff --git a/server/images.go b/server/images.go index a62991f16..688d5dcae 100644 --- a/server/images.go +++ b/server/images.go @@ -34,6 +34,8 @@ import ( "github.com/ollama/ollama/version" ) +var errCapabilityCompletion = errors.New("completion") + type Capability string const CapabilityCompletion = Capability("completion") @@ -62,7 +64,10 @@ type Model struct { Template *template.Template } -func (m *Model) Has(caps ...Capability) bool { +// CheckCapabilities checks if the model has the specified capabilities returning an error describing +// any missing or unknown capabilities +func (m *Model) CheckCapabilities(caps ...Capability) error { + var errs []error for _, cap := range caps { switch cap { case CapabilityCompletion: @@ -81,15 +86,19 @@ func (m *Model) Has(caps ...Capability) bool { } if _, ok := ggml.KV()[fmt.Sprintf("%s.pooling_type", ggml.KV().Architecture())]; ok { - return false + errs = append(errs, errCapabilityCompletion) } default: slog.Error("unknown capability", "capability", cap) - return false + return fmt.Errorf("unknown capability: %s", cap) } } - return true + if err := errors.Join(errs...); err != nil { + return fmt.Errorf("missing capabilities: %w", errors.Join(errs...)) + } + + return nil } func (m *Model) String() string { diff --git a/server/prompt.go b/server/prompt.go index bfc319a50..5016fbe14 100644 --- a/server/prompt.go +++ b/server/prompt.go @@ -1,217 +1,74 @@ package server import ( - "fmt" + "bytes" + "context" "log/slog" - "strings" - - "text/template/parse" + "slices" "github.com/ollama/ollama/api" + "github.com/ollama/ollama/llm" "github.com/ollama/ollama/template" ) -// isResponseNode checks if the node contains .Response -func isResponseNode(node *parse.ActionNode) bool { - for _, cmd := range node.Pipe.Cmds { - for _, arg := range cmd.Args { - if fieldNode, ok := arg.(*parse.FieldNode); ok && len(fieldNode.Ident) > 0 { - if fieldNode.Ident[0] == "Response" { - return true - } - } +func chatPrompt(ctx context.Context, r *runnerRef, msgs []api.Message) (prompt string, images []llm.ImageData, _ error) { + // extract system messages which should always be included + var system []api.Message + msgs = slices.DeleteFunc(msgs, func(m api.Message) bool { + if m.Role == "system" { + system = append(system, m) + return true } - } - return false -} -// formatTemplateForResponse formats the template AST to: -// 1. remove all nodes after the first .Response (if generate=true) -// 2. add a .Response node to the end if it doesn't exist -// TODO(jmorganca): this should recursively cut the template before the first .Response -func formatTemplateForResponse(tmpl *template.Template, generate bool) { - var found bool - for i, node := range tmpl.Tree.Root.Nodes { - if actionNode, ok := node.(*parse.ActionNode); ok { - if isResponseNode(actionNode) { - found = true - if generate { - tmpl.Tree.Root.Nodes = tmpl.Tree.Root.Nodes[:i+1] - break - } - } + return false + }) + + if len(system) == 0 && r.model.System != "" { + // add model system prompt since it wasn't provided + system = append(system, api.Message{Role: "system", Content: r.model.System}) + } + + n := len(msgs) - 1 + for i := n - 1; i >= 0; i-- { + var b bytes.Buffer + if err := r.model.Template.Execute(&b, template.Values{Messages: append(system, msgs[i:]...)}); err != nil { + return "", nil, err } - } - if !found { - // add the response node if it doesn't exist - responseFieldNode := &parse.FieldNode{NodeType: parse.NodeField, Ident: []string{"Response"}} - responsePipeNode := &parse.PipeNode{NodeType: parse.NodePipe, Cmds: []*parse.CommandNode{{NodeType: parse.NodeCommand, Args: []parse.Node{responseFieldNode}}}} - responseActionNode := &parse.ActionNode{NodeType: parse.NodeAction, Pipe: responsePipeNode} - tmpl.Tree.Root.Nodes = append(tmpl.Tree.Root.Nodes, responseActionNode) - } -} - -// Prompt renders a prompt from a template. If generate is set to true, -// the response and parts of the template following it are not rendered -func Prompt(tmpl *template.Template, system, prompt, response string, generate bool) (string, error) { - formatTemplateForResponse(tmpl, generate) - - vars := map[string]any{ - "System": system, - "Prompt": prompt, - "Response": response, - } - - var sb strings.Builder - if err := tmpl.Execute(&sb, vars); err != nil { - return "", err - } - - return sb.String(), nil -} - -func countTokens(tmpl *template.Template, system string, prompt string, response string, encode func(string) ([]int, error)) (int, error) { - rendered, err := Prompt(tmpl, system, prompt, response, false) - if err != nil { - return 0, err - } - - tokens, err := encode(rendered) - if err != nil { - slog.Error("failed to encode prompt", "err", err) - return 0, err - } - - return len(tokens), err -} - -// ChatPrompt builds up a prompt from a series of messages, truncating based on context window size -func ChatPrompt(tmpl *template.Template, messages []api.Message, window int, encode func(string) ([]int, error)) (string, error) { - type prompt struct { - System string - Prompt string - Response string - - images []int - tokens int - } - - var p prompt - - // iterate through messages to build up {system,user,response} prompts - var imgId int - var prompts []prompt - for _, msg := range messages { - switch strings.ToLower(msg.Role) { - case "system": - if p.System != "" || p.Prompt != "" || p.Response != "" { - prompts = append(prompts, p) - p = prompt{} - } - - p.System = msg.Content - case "user": - if p.Prompt != "" || p.Response != "" { - prompts = append(prompts, p) - p = prompt{} - } - - var sb strings.Builder - for range msg.Images { - fmt.Fprintf(&sb, "[img-%d] ", imgId) - p.images = append(p.images, imgId) - imgId += 1 - } - - sb.WriteString(msg.Content) - p.Prompt = sb.String() - case "assistant": - if p.Response != "" { - prompts = append(prompts, p) - p = prompt{} - } - - p.Response = msg.Content - default: - return "", fmt.Errorf("invalid role: %s, role must be one of [system, user, assistant]", msg.Role) - } - } - - // add final prompt - if p.System != "" || p.Prompt != "" || p.Response != "" { - prompts = append(prompts, p) - } - - // calculate token lengths for each prompt, estimating 768 tokens per images - for i, p := range prompts { - tokens, err := countTokens(tmpl, p.System, p.Prompt, p.Response, encode) + s, err := r.llama.Tokenize(ctx, b.String()) if err != nil { - return "", err + return "", nil, err } - prompts[i].tokens = tokens + len(prompts[i].images)*768 - } - - // truncate images and prompts starting from the beginning of the list - // until either one prompt remains or the total tokens fits the context window - // TODO (jmorganca): this doesn't account for the context window room required for the response - for { - var required int - for _, p := range prompts { - required += p.tokens + c := len(s) + if r.model.ProjectorPaths != nil { + for _, m := range msgs[i:] { + // TODO: get image embedding length from project metadata + c += 768 * len(m.Images) + } } - required += 1 // for bos token - - if required <= window { - slog.Debug("prompt now fits in context window", "required", required, "window", window) + if c > r.NumCtx { + slog.Debug("truncating input messages which exceed context length", "truncated", len(msgs[i:])) break + } else { + n = i } - - prompt := &prompts[0] - - if len(prompt.images) > 1 { - img := prompt.images[0] - slog.Debug("prompt longer than context window, removing image", "id", img, "required", required, "window", window) - prompt.images = prompt.images[1:] - prompt.Prompt = strings.Replace(prompt.Prompt, fmt.Sprintf(" [img-%d]", img), "", 1) - prompt.tokens -= 768 - continue - } - - if len(prompts) > 1 { - slog.Debug("required tokens longer than context window, removing first prompt", "prompt", prompts[0].tokens, "required", required, "window", window) - system := prompt.System - prompts = prompts[1:] - - if system != "" && prompts[0].System == "" { - prompts[0].System = system - - tokens, err := countTokens(tmpl, prompts[0].System, prompts[0].Prompt, prompts[0].Response, encode) - if err != nil { - return "", err - } - - prompts[0].tokens = tokens + len(prompts[0].images)*768 - } - - continue - } - - // stop truncating if there's only one prompt left - break } - var sb strings.Builder - for i, p := range prompts { - // last prompt should leave the response unrendered (for completion) - rendered, err := Prompt(tmpl, p.System, p.Prompt, p.Response, i == len(prompts)-1) - if err != nil { - return "", err - } - sb.WriteString(rendered) + var b bytes.Buffer + if err := r.model.Template.Execute(&b, template.Values{Messages: append(system, msgs[n:]...)}); err != nil { + return "", nil, err } - return sb.String(), nil + for _, m := range msgs[n:] { + for _, i := range m.Images { + images = append(images, llm.ImageData{ + ID: len(images), + Data: i, + }) + } + } + + return b.String(), images, nil } diff --git a/server/prompt_test.go b/server/prompt_test.go index 7df58d0bd..59288b46c 100644 --- a/server/prompt_test.go +++ b/server/prompt_test.go @@ -1,215 +1,214 @@ package server import ( + "bytes" + "context" "strings" "testing" "github.com/ollama/ollama/api" + "github.com/ollama/ollama/llm" "github.com/ollama/ollama/template" ) -func TestPrompt(t *testing.T) { - tests := []struct { - name string - template string - system string - prompt string - response string - generate bool - want string - }{ - { - name: "simple prompt", - template: "[INST] {{ .System }} {{ .Prompt }} [/INST]", - system: "You are a Wizard.", - prompt: "What are the potion ingredients?", - want: "[INST] You are a Wizard. What are the potion ingredients? [/INST]", - }, - { - name: "implicit response", - template: "[INST] {{ .System }} {{ .Prompt }} [/INST]", - system: "You are a Wizard.", - prompt: "What are the potion ingredients?", - response: "I don't know.", - want: "[INST] You are a Wizard. What are the potion ingredients? [/INST]I don't know.", - }, - { - name: "response", - template: "[INST] {{ .System }} {{ .Prompt }} [/INST] {{ .Response }}", - system: "You are a Wizard.", - prompt: "What are the potion ingredients?", - response: "I don't know.", - want: "[INST] You are a Wizard. What are the potion ingredients? [/INST] I don't know.", - }, - { - name: "cut", - template: "{{ .System }}{{ .Prompt }}{{ .Response }}", - system: "You are a Wizard.", - prompt: "What are the potion ingredients?", - response: "I don't know.", - generate: true, - want: "You are a Wizard.What are the potion ingredients?I don't know.", - }, - { - name: "nocut", - template: "{{ .System }}{{ .Prompt }}{{ .Response }}", - system: "You are a Wizard.", - prompt: "What are the potion ingredients?", - response: "I don't know.", - want: "You are a Wizard.What are the potion ingredients?I don't know.", - }, +type mock struct { + llm.LlamaServer +} + +func (m mock) Tokenize(_ context.Context, s string) (tokens []int, err error) { + for range strings.Fields(s) { + tokens = append(tokens, len(tokens)) } - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - tmpl, err := template.Parse(tc.template) - if err != nil { - t.Fatal(err) - } - - got, err := Prompt(tmpl, tc.system, tc.prompt, tc.response, tc.generate) - if err != nil { - t.Errorf("error = %v", err) - } - - if got != tc.want { - t.Errorf("got = %v, want %v", got, tc.want) - } - }) - } + return } func TestChatPrompt(t *testing.T) { - tests := []struct { - name string - template string - messages []api.Message - window int - want string + type expect struct { + prompt string + images [][]byte + } + + cases := []struct { + name string + limit int + msgs []api.Message + expect }{ { - name: "simple prompt", - template: "[INST] {{ .Prompt }} [/INST]", - messages: []api.Message{ - {Role: "user", Content: "Hello"}, + name: "messages", + limit: 64, + msgs: []api.Message{ + {Role: "user", Content: "You're a test, Harry!"}, + {Role: "assistant", Content: "I-I'm a what?"}, + {Role: "user", Content: "A test. And a thumping good one at that, I'd wager."}, + }, + expect: expect{ + prompt: "You're a test, Harry! I-I'm a what? A test. And a thumping good one at that, I'd wager. ", }, - window: 1024, - want: "[INST] Hello [/INST]", }, { - name: "with system message", - template: "[INST] {{ if .System }}<>{{ .System }}<> {{ end }}{{ .Prompt }} [/INST]", - messages: []api.Message{ - {Role: "system", Content: "You are a Wizard."}, - {Role: "user", Content: "Hello"}, + name: "truncate messages", + limit: 1, + msgs: []api.Message{ + {Role: "user", Content: "You're a test, Harry!"}, + {Role: "assistant", Content: "I-I'm a what?"}, + {Role: "user", Content: "A test. And a thumping good one at that, I'd wager."}, + }, + expect: expect{ + prompt: "A test. And a thumping good one at that, I'd wager. ", }, - window: 1024, - want: "[INST] <>You are a Wizard.<> Hello [/INST]", }, { - name: "with response", - template: "[INST] {{ if .System }}<>{{ .System }}<> {{ end }}{{ .Prompt }} [/INST] {{ .Response }}", - messages: []api.Message{ - {Role: "system", Content: "You are a Wizard."}, - {Role: "user", Content: "Hello"}, - {Role: "assistant", Content: "I am?"}, + name: "truncate messages with image", + limit: 64, + msgs: []api.Message{ + {Role: "user", Content: "You're a test, Harry!"}, + {Role: "assistant", Content: "I-I'm a what?"}, + {Role: "user", Content: "A test. And a thumping good one at that, I'd wager.", Images: []api.ImageData{[]byte("something")}}, + }, + expect: expect{ + prompt: "[img-0] A test. And a thumping good one at that, I'd wager. ", + images: [][]byte{ + []byte("something"), + }, }, - window: 1024, - want: "[INST] <>You are a Wizard.<> Hello [/INST] I am?", }, { - name: "with implicit response", - template: "[INST] {{ if .System }}<>{{ .System }}<> {{ end }}{{ .Prompt }} [/INST]", - messages: []api.Message{ - {Role: "system", Content: "You are a Wizard."}, - {Role: "user", Content: "Hello"}, - {Role: "assistant", Content: "I am?"}, + name: "truncate messages with images", + limit: 64, + msgs: []api.Message{ + {Role: "user", Content: "You're a test, Harry!", Images: []api.ImageData{[]byte("something")}}, + {Role: "assistant", Content: "I-I'm a what?"}, + {Role: "user", Content: "A test. And a thumping good one at that, I'd wager.", Images: []api.ImageData{[]byte("somethingelse")}}, + }, + expect: expect{ + prompt: "[img-0] A test. And a thumping good one at that, I'd wager. ", + images: [][]byte{ + []byte("somethingelse"), + }, }, - window: 1024, - want: "[INST] <>You are a Wizard.<> Hello [/INST]I am?", }, { - name: "with conversation", - template: "[INST] {{ if .System }}<>{{ .System }}<> {{ end }}{{ .Prompt }} [/INST] {{ .Response }} ", - messages: []api.Message{ - {Role: "system", Content: "You are a Wizard."}, - {Role: "user", Content: "What are the potion ingredients?"}, - {Role: "assistant", Content: "sugar"}, - {Role: "user", Content: "Anything else?"}, + name: "messages with images", + limit: 2048, + msgs: []api.Message{ + {Role: "user", Content: "You're a test, Harry!", Images: []api.ImageData{[]byte("something")}}, + {Role: "assistant", Content: "I-I'm a what?"}, + {Role: "user", Content: "A test. And a thumping good one at that, I'd wager.", Images: []api.ImageData{[]byte("somethingelse")}}, + }, + expect: expect{ + prompt: "[img-0] You're a test, Harry! I-I'm a what? [img-1] A test. And a thumping good one at that, I'd wager. ", + images: [][]byte{ + []byte("something"), + []byte("somethingelse"), + }, }, - window: 1024, - want: "[INST] <>You are a Wizard.<> What are the potion ingredients? [/INST] sugar [INST] Anything else? [/INST] ", }, { - name: "with truncation", - template: "{{ .System }} {{ .Prompt }} {{ .Response }} ", - messages: []api.Message{ - {Role: "system", Content: "You are a Wizard."}, - {Role: "user", Content: "Hello"}, - {Role: "assistant", Content: "I am?"}, - {Role: "user", Content: "Why is the sky blue?"}, - {Role: "assistant", Content: "The sky is blue from rayleigh scattering"}, + name: "message with image tag", + limit: 2048, + msgs: []api.Message{ + {Role: "user", Content: "You're a test, Harry! [img]", Images: []api.ImageData{[]byte("something")}}, + {Role: "assistant", Content: "I-I'm a what?"}, + {Role: "user", Content: "A test. And a thumping good one at that, I'd wager.", Images: []api.ImageData{[]byte("somethingelse")}}, + }, + expect: expect{ + prompt: "You're a test, Harry! [img-0] I-I'm a what? [img-1] A test. And a thumping good one at that, I'd wager. ", + images: [][]byte{ + []byte("something"), + []byte("somethingelse"), + }, }, - window: 10, - want: "You are a Wizard. Why is the sky blue? The sky is blue from rayleigh scattering", }, { - name: "images", - template: "{{ .System }} {{ .Prompt }}", - messages: []api.Message{ - {Role: "system", Content: "You are a Wizard."}, - {Role: "user", Content: "Hello", Images: []api.ImageData{[]byte("base64")}}, + name: "messages with interleaved images", + limit: 2048, + msgs: []api.Message{ + {Role: "user", Content: "You're a test, Harry!"}, + {Role: "user", Images: []api.ImageData{[]byte("something")}}, + {Role: "user", Images: []api.ImageData{[]byte("somethingelse")}}, + {Role: "assistant", Content: "I-I'm a what?"}, + {Role: "user", Content: "A test. And a thumping good one at that, I'd wager."}, + }, + expect: expect{ + prompt: "You're a test, Harry!\n\n[img-0]\n\n[img-1] I-I'm a what? A test. And a thumping good one at that, I'd wager. ", + images: [][]byte{ + []byte("something"), + []byte("somethingelse"), + }, }, - window: 1024, - want: "You are a Wizard. [img-0] Hello", }, { - name: "images truncated", - template: "{{ .System }} {{ .Prompt }}", - messages: []api.Message{ - {Role: "system", Content: "You are a Wizard."}, - {Role: "user", Content: "Hello", Images: []api.ImageData{[]byte("img1"), []byte("img2")}}, + name: "truncate message with interleaved images", + limit: 1024, + msgs: []api.Message{ + {Role: "user", Content: "You're a test, Harry!"}, + {Role: "user", Images: []api.ImageData{[]byte("something")}}, + {Role: "user", Images: []api.ImageData{[]byte("somethingelse")}}, + {Role: "assistant", Content: "I-I'm a what?"}, + {Role: "user", Content: "A test. And a thumping good one at that, I'd wager."}, + }, + expect: expect{ + prompt: "[img-0] I-I'm a what? A test. And a thumping good one at that, I'd wager. ", + images: [][]byte{ + []byte("somethingelse"), + }, }, - window: 1024, - want: "You are a Wizard. [img-0] [img-1] Hello", }, { - name: "empty list", - template: "{{ .System }} {{ .Prompt }}", - messages: []api.Message{}, - window: 1024, - want: "", - }, - { - name: "empty prompt", - template: "[INST] {{ if .System }}<>{{ .System }}<> {{ end }}{{ .Prompt }} [/INST] {{ .Response }} ", - messages: []api.Message{ - {Role: "user", Content: ""}, + name: "message with system prompt", + limit: 2048, + msgs: []api.Message{ + {Role: "system", Content: "You are the Test Who Lived."}, + {Role: "user", Content: "You're a test, Harry!"}, + {Role: "assistant", Content: "I-I'm a what?"}, + {Role: "user", Content: "A test. And a thumping good one at that, I'd wager."}, + }, + expect: expect{ + prompt: "You're a test, Harry! I-I'm a what? You are the Test Who Lived. A test. And a thumping good one at that, I'd wager. ", }, - window: 1024, - want: "", }, } - encode := func(s string) ([]int, error) { - words := strings.Fields(s) - return make([]int, len(words)), nil + tmpl, err := template.Parse(` +{{- if .System }}{{ .System }} {{ end }} +{{- if .Prompt }}{{ .Prompt }} {{ end }} +{{- if .Response }}{{ .Response }} {{ end }}`) + if err != nil { + t.Fatal(err) } - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - tmpl, err := template.Parse(tc.template) + for _, tt := range cases { + t.Run(tt.name, func(t *testing.T) { + r := runnerRef{ + llama: mock{}, + model: &Model{Template: tmpl, ProjectorPaths: []string{"vision"}}, + Options: &api.Options{}, + } + + r.NumCtx = tt.limit + prompt, images, err := chatPrompt(context.TODO(), &r, tt.msgs) if err != nil { t.Fatal(err) } - got, err := ChatPrompt(tmpl, tc.messages, tc.window, encode) - if err != nil { - t.Errorf("error = %v", err) + if tt.prompt != prompt { + t.Errorf("expected %q, got %q", tt.prompt, prompt) } - if got != tc.want { - t.Errorf("got: %q, want: %q", got, tc.want) + if len(images) != len(tt.images) { + t.Fatalf("expected %d images, got %d", len(tt.images), len(images)) + } + + for i := range images { + if images[i].ID != i { + t.Errorf("expected ID %d, got %d", i, images[i].ID) + } + + if !bytes.Equal(images[i].Data, tt.images[i]) { + t.Errorf("expected %q, got %q", tt.images[i], images[i]) + } } }) } diff --git a/server/routes.go b/server/routes.go index ac6b713a7..35e64511b 100644 --- a/server/routes.go +++ b/server/routes.go @@ -1,13 +1,13 @@ package server import ( + "bytes" "cmp" "context" "encoding/json" "errors" "fmt" "io" - "io/fs" "log/slog" "net" "net/http" @@ -67,163 +67,140 @@ func modelOptions(model *Model, requestOpts map[string]interface{}) (api.Options return opts, nil } -func isSupportedImageType(image []byte) bool { - contentType := http.DetectContentType(image) - allowedTypes := []string{"image/jpeg", "image/jpg", "image/png"} - return slices.Contains(allowedTypes, contentType) +func (s *Server) scheduleRunner(ctx context.Context, name string, caps []Capability, requestOpts map[string]any, keepAlive *api.Duration) (*runnerRef, error) { + if name == "" { + return nil, errors.New("model is required") + } + + model, err := GetModel(name) + if err != nil { + return nil, err + } + + if err := model.CheckCapabilities(caps...); err != nil { + return nil, fmt.Errorf("%s %w", name, err) + } + + opts, err := modelOptions(model, requestOpts) + if err != nil { + return nil, err + } + + runnerCh, errCh := s.sched.GetRunner(ctx, model, opts, keepAlive) + var runner *runnerRef + select { + case runner = <-runnerCh: + case err = <-errCh: + return nil, err + } + + return runner, nil } func (s *Server) GenerateHandler(c *gin.Context) { - checkpointStart := time.Now() var req api.GenerateRequest - err := c.ShouldBindJSON(&req) - - switch { - case errors.Is(err, io.EOF): + if err := c.ShouldBindJSON(&req); errors.Is(err, io.EOF) { c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "missing request body"}) return - case err != nil: + } else if err != nil { c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } - // validate the request - switch { - case req.Model == "": - c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "model is required"}) + if req.Format != "" && req.Format != "json" { + c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "format must be empty or \"json\""}) return - case len(req.Format) > 0 && req.Format != "json": - c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "format must be json"}) - return - case req.Raw && (req.Template != "" || req.System != "" || len(req.Context) > 0): + } else if req.Raw && (req.Template != "" || req.System != "" || len(req.Context) > 0) { c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "raw mode does not support template, system, or context"}) return } - for _, img := range req.Images { - if !isSupportedImageType(img) { - c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "unsupported image format"}) - return - } - } - - model, err := GetModel(req.Model) - if err != nil { - var pErr *fs.PathError - if errors.As(err, &pErr) { - c.JSON(http.StatusNotFound, gin.H{"error": fmt.Sprintf("model '%s' not found, try pulling it first", req.Model)}) - return - } - c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + caps := []Capability{CapabilityCompletion} + r, err := s.scheduleRunner(c.Request.Context(), req.Model, caps, req.Options, req.KeepAlive) + if errors.Is(err, errCapabilityCompletion) { + c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Sprintf("%q does not support generate", req.Model)}) + return + } else if err != nil { + handleScheduleError(c, err) return } - if !model.Has(CapabilityCompletion) { - c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Sprintf("%s does not support generate", req.Model)}) - return + images := make([]llm.ImageData, len(req.Images)) + for i := range req.Images { + images[i] = llm.ImageData{ID: i, Data: req.Images[i]} } - opts, err := modelOptions(model, req.Options) - if err != nil { - c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) - return - } - - rCh, eCh := s.sched.GetRunner(c.Request.Context(), model, opts, req.KeepAlive) - var runner *runnerRef - select { - case runner = <-rCh: - case err = <-eCh: - handleErrorResponse(c, err) - return - } - - // an empty request loads the model - // note: for a short while template was used in lieu - // of `raw` mode so we need to check for it too - if req.Prompt == "" && req.Template == "" && req.System == "" { - c.JSON(http.StatusOK, api.GenerateResponse{ - CreatedAt: time.Now().UTC(), - Model: req.Model, - Done: true, - DoneReason: "load", - }) - return - } - - tmpl, err := template.Parse(req.Template) - if err != nil { - c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) - return - } - - checkpointLoaded := time.Now() - - var prompt string - switch { - case req.Raw: - prompt = req.Prompt - case req.Prompt != "": - if req.Template == "" { - tmpl = model.Template + prompt := req.Prompt + if !req.Raw { + var msgs []api.Message + if req.System != "" { + msgs = append(msgs, api.Message{Role: "system", Content: req.System}) + } else if r.model.System != "" { + msgs = append(msgs, api.Message{Role: "system", Content: r.model.System}) } - if req.System == "" { - req.System = model.System + if req.Prompt != "" { + for _, i := range images { + msgs = append(msgs, api.Message{Role: "user", Content: fmt.Sprintf("[img-%d]", i.ID)}) + } + + msgs = append(msgs, api.Message{Role: "user", Content: req.Prompt}) } - slog.Debug("generate handler", "prompt", req.Prompt) - slog.Debug("generate handler", "template", req.Template) - slog.Debug("generate handler", "system", req.System) - - var sb strings.Builder - for i := range req.Images { - fmt.Fprintf(&sb, "[img-%d] ", i) - } - - sb.WriteString(req.Prompt) - - p, err := Prompt(tmpl, req.System, sb.String(), "", true) - if err != nil { - c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + if len(msgs) == 0 { + c.JSON(http.StatusOK, api.GenerateResponse{ + Model: req.Model, + CreatedAt: time.Now().UTC(), + Done: true, + DoneReason: "load", + }) return } - sb.Reset() + tmpl := r.model.Template + if req.Template != "" { + tmpl, err = template.Parse(req.Template) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + } + + var b bytes.Buffer if req.Context != nil { - prev, err := runner.llama.Detokenize(c.Request.Context(), req.Context) + s, err := r.llama.Detokenize(c.Request.Context(), req.Context) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } - sb.WriteString(prev) + b.WriteString(s) } - sb.WriteString(p) + if err := tmpl.Execute(&b, template.Values{Messages: msgs}); err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } - prompt = sb.String() + prompt = b.String() } - slog.Debug("generate handler", "prompt", prompt) + slog.Debug("generate request", "prompt", prompt, "images", images) ch := make(chan any) - var generated strings.Builder go func() { defer close(ch) - - fn := func(r llm.CompletionResponse) { - // Build up the full response - if _, err := generated.WriteString(r.Content); err != nil { - ch <- gin.H{"error": err.Error()} - return - } - - resp := api.GenerateResponse{ + if err := r.llama.Completion(c.Request.Context(), llm.CompletionRequest{ + Prompt: prompt, + Images: images, + Format: req.Format, + Options: *r.Options, + }, func(r llm.CompletionResponse) { + ch <- api.GenerateResponse{ Model: req.Model, CreatedAt: time.Now().UTC(), - Done: r.Done, Response: r.Content, + Done: r.Done, DoneReason: r.DoneReason, Metrics: api.Metrics{ PromptEvalCount: r.PromptEvalCount, @@ -232,77 +209,35 @@ func (s *Server) GenerateHandler(c *gin.Context) { EvalDuration: r.EvalDuration, }, } - - if r.Done { - resp.TotalDuration = time.Since(checkpointStart) - resp.LoadDuration = checkpointLoaded.Sub(checkpointStart) - - if !req.Raw { - p, err := Prompt(tmpl, req.System, req.Prompt, generated.String(), false) - if err != nil { - c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) - return - } - - // TODO (jmorganca): encode() should not strip special tokens - tokens, err := runner.llama.Tokenize(c.Request.Context(), p) - if err != nil { - ch <- gin.H{"error": err.Error()} - return - } - - resp.Context = append(req.Context, tokens...) - } - } - - ch <- resp - } - - var images []llm.ImageData - for i := range req.Images { - images = append(images, llm.ImageData{ - ID: i, - Data: req.Images[i], - }) - } - - // Start prediction - req := llm.CompletionRequest{ - Prompt: prompt, - Format: req.Format, - Images: images, - Options: opts, - } - if err := runner.llama.Completion(c.Request.Context(), req, fn); err != nil { + }); err != nil { ch <- gin.H{"error": err.Error()} } }() if req.Stream != nil && !*req.Stream { - // Accumulate responses into the final response - var final api.GenerateResponse + var r api.GenerateResponse var sb strings.Builder - for resp := range ch { - switch r := resp.(type) { + for rr := range ch { + switch t := rr.(type) { case api.GenerateResponse: - sb.WriteString(r.Response) - final = r + sb.WriteString(t.Response) + r = t case gin.H: - if errorMsg, ok := r["error"].(string); ok { - c.JSON(http.StatusInternalServerError, gin.H{"error": errorMsg}) - return - } else { - c.JSON(http.StatusInternalServerError, gin.H{"error": "unexpected error format in response"}) - return + msg, ok := t["error"].(string) + if !ok { + msg = "unexpected error format in response" } + + c.JSON(http.StatusInternalServerError, gin.H{"error": msg}) + return default: - c.JSON(http.StatusInternalServerError, gin.H{"error": "unexpected error"}) + c.JSON(http.StatusInternalServerError, gin.H{"error": "unexpected response"}) return } } - final.Response = sb.String() - c.JSON(http.StatusOK, final) + r.Response = sb.String() + c.JSON(http.StatusOK, r) return } @@ -311,44 +246,17 @@ func (s *Server) GenerateHandler(c *gin.Context) { func (s *Server) EmbeddingsHandler(c *gin.Context) { var req api.EmbeddingRequest - err := c.ShouldBindJSON(&req) - switch { - case errors.Is(err, io.EOF): + if err := c.ShouldBindJSON(&req); errors.Is(err, io.EOF) { c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "missing request body"}) return - case err != nil: + } else if err != nil { c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } - if req.Model == "" { - c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "model is required"}) - return - } - - model, err := GetModel(req.Model) + r, err := s.scheduleRunner(c.Request.Context(), req.Model, []Capability{}, req.Options, req.KeepAlive) if err != nil { - var pErr *fs.PathError - if errors.As(err, &pErr) { - c.JSON(http.StatusNotFound, gin.H{"error": fmt.Sprintf("model '%s' not found, try pulling it first", req.Model)}) - return - } - c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) - return - } - - opts, err := modelOptions(model, req.Options) - if err != nil { - c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) - return - } - - rCh, eCh := s.sched.GetRunner(c.Request.Context(), model, opts, req.KeepAlive) - var runner *runnerRef - select { - case runner = <-rCh: - case err = <-eCh: - handleErrorResponse(c, err) + handleScheduleError(c, err) return } @@ -358,17 +266,14 @@ func (s *Server) EmbeddingsHandler(c *gin.Context) { return } - embedding, err := runner.llama.Embedding(c.Request.Context(), req.Prompt) + embedding, err := r.llama.Embedding(c.Request.Context(), req.Prompt) if err != nil { slog.Info(fmt.Sprintf("embedding generation failed: %v", err)) c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to generate embedding"}) return } - resp := api.EmbeddingResponse{ - Embedding: embedding, - } - c.JSON(http.StatusOK, resp) + c.JSON(http.StatusOK, api.EmbeddingResponse{Embedding: embedding}) } func (s *Server) PullModelHandler(c *gin.Context) { @@ -649,9 +554,9 @@ func GetModelInfo(req api.ShowRequest) (*api.ShowResponse, error) { } } - msgs := make([]api.Message, 0) - for _, msg := range m.Messages { - msgs = append(msgs, api.Message{Role: msg.Role, Content: msg.Content}) + msgs := make([]api.Message, len(m.Messages)) + for i, msg := range m.Messages { + msgs[i] = api.Message{Role: msg.Role, Content: msg.Content} } n := model.ParseName(req.Model) @@ -1214,132 +1119,55 @@ func (s *Server) ProcessHandler(c *gin.Context) { c.JSON(http.StatusOK, api.ProcessResponse{Models: models}) } -// ChatPrompt builds up a prompt from a series of messages for the currently `loaded` model -func chatPrompt(ctx context.Context, runner *runnerRef, template *template.Template, messages []api.Message, numCtx int) (string, error) { - encode := func(s string) ([]int, error) { - return runner.llama.Tokenize(ctx, s) - } - - prompt, err := ChatPrompt(template, messages, numCtx, encode) - if err != nil { - return "", err - } - - return prompt, nil -} - func (s *Server) ChatHandler(c *gin.Context) { - checkpointStart := time.Now() - var req api.ChatRequest - err := c.ShouldBindJSON(&req) - switch { - case errors.Is(err, io.EOF): + if err := c.ShouldBindJSON(&req); errors.Is(err, io.EOF) { c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "missing request body"}) return - case err != nil: + } else if err != nil { c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } - // validate the request - switch { - case req.Model == "": - c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "model is required"}) + caps := []Capability{CapabilityCompletion} + r, err := s.scheduleRunner(c.Request.Context(), req.Model, caps, req.Options, req.KeepAlive) + if errors.Is(err, errCapabilityCompletion) { + c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Sprintf("%q does not support chat", req.Model)}) return - case len(req.Format) > 0 && req.Format != "json": - c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "format must be json"}) + } else if err != nil { + handleScheduleError(c, err) return } - model, err := GetModel(req.Model) - if err != nil { - var pErr *fs.PathError - if errors.As(err, &pErr) { - c.JSON(http.StatusNotFound, gin.H{"error": fmt.Sprintf("model '%s' not found, try pulling it first", req.Model)}) - return - } - c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) - return - } - - if !model.Has(CapabilityCompletion) { - c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Sprintf("%s does not support chat", req.Model)}) - return - } - - opts, err := modelOptions(model, req.Options) - if err != nil { - c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) - return - } - - rCh, eCh := s.sched.GetRunner(c.Request.Context(), model, opts, req.KeepAlive) - var runner *runnerRef - select { - case runner = <-rCh: - case err = <-eCh: - handleErrorResponse(c, err) - return - } - - checkpointLoaded := time.Now() - - // if the first message is not a system message, then add the model's default system message - if len(req.Messages) > 0 && req.Messages[0].Role != "system" { - req.Messages = append([]api.Message{ - { - Role: "system", - Content: model.System, - }, - }, req.Messages...) - } - - prompt, err := chatPrompt(c.Request.Context(), runner, model.Template, req.Messages, opts.NumCtx) - if err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) - return - } - - // an empty request loads the model - if len(req.Messages) == 0 || prompt == "" { - resp := api.ChatResponse{ - CreatedAt: time.Now().UTC(), + if len(req.Messages) == 0 { + c.JSON(http.StatusOK, api.ChatResponse{ Model: req.Model, + CreatedAt: time.Now().UTC(), + Message: api.Message{Role: "assistant"}, Done: true, DoneReason: "load", - Message: api.Message{Role: "assistant"}, - } - c.JSON(http.StatusOK, resp) + }) return } - // only send images that are in the prompt - var i int - var images []llm.ImageData - for _, m := range req.Messages { - for _, img := range m.Images { - if !isSupportedImageType(img) { - c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "unsupported image format"}) - return - } - - if strings.Contains(prompt, fmt.Sprintf("[img-%d]", i)) { - images = append(images, llm.ImageData{Data: img, ID: i}) - } - i += 1 - } + prompt, images, err := chatPrompt(c.Request.Context(), r, req.Messages) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return } - slog.Debug("chat handler", "prompt", prompt, "images", len(images)) + slog.Debug("chat request", "images", len(images), "prompt", prompt) ch := make(chan any) - go func() { defer close(ch) - - fn := func(r llm.CompletionResponse) { - resp := api.ChatResponse{ + if err := r.llama.Completion(c.Request.Context(), llm.CompletionRequest{ + Prompt: prompt, + Images: images, + Format: req.Format, + Options: *r.Options, + }, func(r llm.CompletionResponse) { + ch <- api.ChatResponse{ Model: req.Model, CreatedAt: time.Now().UTC(), Message: api.Message{Role: "assistant", Content: r.Content}, @@ -1352,64 +1180,48 @@ func (s *Server) ChatHandler(c *gin.Context) { EvalDuration: r.EvalDuration, }, } - - if r.Done { - resp.TotalDuration = time.Since(checkpointStart) - resp.LoadDuration = checkpointLoaded.Sub(checkpointStart) - } - - ch <- resp - } - - if err := runner.llama.Completion(c.Request.Context(), llm.CompletionRequest{ - Prompt: prompt, - Format: req.Format, - Images: images, - Options: opts, - }, fn); err != nil { + }); err != nil { ch <- gin.H{"error": err.Error()} } }() if req.Stream != nil && !*req.Stream { - // Accumulate responses into the final response - var final api.ChatResponse + var r api.ChatResponse var sb strings.Builder - for resp := range ch { - switch r := resp.(type) { + for rr := range ch { + switch t := rr.(type) { case api.ChatResponse: - sb.WriteString(r.Message.Content) - final = r + sb.WriteString(t.Message.Content) + r = t case gin.H: - if errorMsg, ok := r["error"].(string); ok { - c.JSON(http.StatusInternalServerError, gin.H{"error": errorMsg}) - return - } else { - c.JSON(http.StatusInternalServerError, gin.H{"error": "unexpected error format in response"}) - return + msg, ok := t["error"].(string) + if !ok { + msg = "unexpected error format in response" } + + c.JSON(http.StatusInternalServerError, gin.H{"error": msg}) + return default: - c.JSON(http.StatusInternalServerError, gin.H{"error": "unexpected error"}) + c.JSON(http.StatusInternalServerError, gin.H{"error": "unexpected response"}) return } } - final.Message = api.Message{Role: "assistant", Content: sb.String()} - c.JSON(http.StatusOK, final) + r.Message.Content = sb.String() + c.JSON(http.StatusOK, r) return } streamResponse(c, ch) } -func handleErrorResponse(c *gin.Context, err error) { - if errors.Is(err, context.Canceled) { +func handleScheduleError(c *gin.Context, err error) { + switch { + case errors.Is(err, context.Canceled): c.JSON(499, gin.H{"error": "request canceled"}) - return - } - if errors.Is(err, ErrMaxQueue) { + case errors.Is(err, ErrMaxQueue): c.JSON(http.StatusServiceUnavailable, gin.H{"error": err.Error()}) - return + default: + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) } - c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) } diff --git a/template/template.go b/template/template.go index d15f7156f..cfba5a238 100644 --- a/template/template.go +++ b/template/template.go @@ -5,6 +5,7 @@ import ( "embed" "encoding/json" "errors" + "fmt" "io" "math" "slices" @@ -14,6 +15,7 @@ import ( "text/template/parse" "github.com/agnivade/levenshtein" + "github.com/ollama/ollama/api" "golang.org/x/exp/maps" ) @@ -74,30 +76,78 @@ func Named(s string) (*named, error) { return nil, errors.New("no matching template found") } +var DefaultTemplate, _ = Parse("{{ .Prompt }}") + type Template struct { *template.Template raw string } +var response = parse.ActionNode{ + NodeType: parse.NodeAction, + Pipe: &parse.PipeNode{ + NodeType: parse.NodePipe, + Cmds: []*parse.CommandNode{ + { + NodeType: parse.NodeCommand, + Args: []parse.Node{ + &parse.FieldNode{ + NodeType: parse.NodeField, + Ident: []string{"Response"}, + }, + }, + }, + }, + }, +} + +func Parse(s string) (*Template, error) { + tmpl := template.New("").Option("missingkey=zero").Funcs(template.FuncMap{ + "toJson": func(v any) string { + b, err := json.Marshal(v) + if err != nil { + return "" + } + + return string(b) + }, + "isLastMessage": func(s []*api.Message, m *api.Message) bool { + for i := len(s) - 1; i >= 0; i-- { + if m.Role != s[i].Role { + continue + } + + return m == s[i] + } + + return false + }, + }) + + tmpl, err := tmpl.Parse(s) + if err != nil { + return nil, err + } + + t := Template{Template: tmpl, raw: s} + if vars := t.Vars(); !slices.Contains(vars, "messages") && !slices.Contains(vars, "response") { + // touch up the template and append {{ .Response }} + tmpl.Tree.Root.Nodes = append(tmpl.Tree.Root.Nodes, &response) + } + + return &t, nil +} + func (t *Template) String() string { return t.raw } -var DefaultTemplate, _ = Parse("{{ .Prompt }}") - -func Parse(s string) (*Template, error) { - t, err := template.New("").Option("missingkey=zero").Parse(s) - if err != nil { - return nil, err - } - - return &Template{Template: t, raw: s}, nil -} - func (t *Template) Vars() []string { var vars []string - for _, n := range t.Tree.Root.Nodes { - vars = append(vars, parseNode(n)...) + for _, tt := range t.Templates() { + for _, n := range tt.Root.Nodes { + vars = append(vars, parseNode(n)...) + } } set := make(map[string]struct{}) @@ -110,6 +160,97 @@ func (t *Template) Vars() []string { return vars } +type Values struct { + Messages []api.Message +} + +func (t *Template) Execute(w io.Writer, v Values) error { + system, collated := collate(v.Messages) + if slices.Contains(t.Vars(), "messages") { + return t.Template.Execute(w, map[string]any{ + "System": system, + "Messages": collated, + }) + } + + var b bytes.Buffer + var prompt, response string + for i, m := range collated { + if m.Role == "user" { + prompt = m.Content + } else { + response = m.Content + } + + if i != len(collated)-1 && prompt != "" && response != "" { + if err := t.Template.Execute(&b, map[string]any{ + "System": "", + "Prompt": prompt, + "Response": response, + }); err != nil { + return err + } + + prompt = "" + response = "" + } + } + + var cut bool + tree := t.Template.Copy() + // for the last message, cut everything after "{{ .Response }}" + tree.Root.Nodes = slices.DeleteFunc(tree.Root.Nodes, func(n parse.Node) bool { + if slices.Contains(parseNode(n), "Response") { + cut = true + } + + return cut + }) + + if err := template.Must(template.New("").AddParseTree("", tree)).Execute(&b, map[string]any{ + "System": system, + "Prompt": prompt, + }); err != nil { + return err + } + + _, err := io.Copy(w, &b) + return err +} + +func collate(msgs []api.Message) (system string, collated []*api.Message) { + var n int + for i := range msgs { + msg := msgs[i] + if msg.Role == "system" { + if system != "" { + system += "\n\n" + } + + system += msg.Content + continue + } + + for range msg.Images { + imageTag := fmt.Sprintf("[img-%d]", n) + if !strings.Contains(msg.Content, "[img]") { + msg.Content = strings.TrimSpace("[img] " + msg.Content) + } + + msg.Content = strings.Replace(msg.Content, "[img]", imageTag, 1) + n++ + } + + if len(collated) > 0 && collated[len(collated)-1].Role == msg.Role { + collated[len(collated)-1].Content += "\n\n" + msg.Content + } else { + collated = append(collated, &msg) + } + } + + return +} + func parseNode(n parse.Node) []string { switch n := n.(type) { case *parse.ActionNode: @@ -152,6 +293,8 @@ func parseNode(n parse.Node) []string { return names case *parse.FieldNode: return n.Ident + case *parse.TemplateNode: + return parseNode(n.Pipe) } return nil diff --git a/template/template_test.go b/template/template_test.go index eda4634f4..5d5dad4b2 100644 --- a/template/template_test.go +++ b/template/template_test.go @@ -11,6 +11,7 @@ import ( "testing" "text/template" + "github.com/ollama/ollama/api" "github.com/ollama/ollama/llm" ) @@ -64,13 +65,12 @@ func TestParse(t *testing.T) { template string vars []string }{ - {"{{ .Prompt }}", []string{"prompt"}}, - {"{{ .System }} {{ .Prompt }}", []string{"prompt", "system"}}, + {"{{ .Prompt }}", []string{"prompt", "response"}}, + {"{{ .System }} {{ .Prompt }}", []string{"prompt", "response", "system"}}, {"{{ .System }} {{ .Prompt }} {{ .Response }}", []string{"prompt", "response", "system"}}, - {"{{ with .Tools }}{{ . }}{{ end }} {{ .System }} {{ .Prompt }}", []string{"prompt", "system", "tools"}}, + {"{{ with .Tools }}{{ . }}{{ end }} {{ .System }} {{ .Prompt }}", []string{"prompt", "response", "system", "tools"}}, {"{{ range .Messages }}{{ .Role }} {{ .Content }}{{ end }}", []string{"content", "messages", "role"}}, {"{{ range .Messages }}{{ if eq .Role \"system\" }}SYSTEM: {{ .Content }}{{ else if eq .Role \"user\" }}USER: {{ .Content }}{{ else if eq .Role \"assistant\" }}ASSISTANT: {{ .Content }}{{ end }}{{ end }}", []string{"content", "messages", "role"}}, - {"{{ .Prompt }} {{ .Suffix }}", []string{"prompt", "suffix"}}, } for _, tt := range cases { @@ -87,3 +87,148 @@ func TestParse(t *testing.T) { }) } } + +func TestExecuteWithMessages(t *testing.T) { + cases := []struct { + templates []string + values Values + expected string + }{ + { + []string{ + `[INST] {{ if .System }}{{ .System }}{{ print "\n\n" }}{{ end }}{{ .Prompt }}[/INST] `, + `[INST] {{ if .System }}{{ .System }}{{ print "\n\n" }}{{ end }}{{ .Prompt }}[/INST] {{ .Response }}`, + `{{- range .Messages }} +{{- if eq .Role "user" }}[INST] {{ if and (isLastMessage $.Messages .) $.System }}{{ $.System }}{{ print "\n\n" }} +{{- end }}{{ .Content }}[/INST] {{ else if eq .Role "assistant" }}{{ .Content }} +{{- end }} +{{- end }}`, + }, + Values{ + Messages: []api.Message{ + {Role: "user", Content: "Hello friend!"}, + {Role: "assistant", Content: "Hello human!"}, + {Role: "user", Content: "Yay!"}, + }, + }, + `[INST] Hello friend![/INST] Hello human![INST] Yay![/INST] `, + }, + { + []string{ + `[INST] {{ if .System }}{{ .System }}{{ print "\n\n" }}{{ end }}{{ .Prompt }}[/INST] `, + `[INST] {{ if .System }}{{ .System }}{{ print "\n\n" }}{{ end }}{{ .Prompt }}[/INST] {{ .Response }}`, + ` +{{- range .Messages }} +{{- if eq .Role "user" }}[INST] {{ if and (isLastMessage $.Messages .) $.System }}{{ $.System }}{{ print "\n\n" }} +{{- end }}{{ .Content }}[/INST] {{ else if eq .Role "assistant" }}{{ .Content }} +{{- end }} +{{- end }}`, + }, + Values{ + Messages: []api.Message{ + {Role: "system", Content: "You are a helpful assistant!"}, + {Role: "user", Content: "Hello friend!"}, + {Role: "assistant", Content: "Hello human!"}, + {Role: "user", Content: "Yay!"}, + }, + }, + `[INST] Hello friend![/INST] Hello human![INST] You are a helpful assistant! + +Yay![/INST] `, + }, + { + []string{ + `{{ if .System }}<|im_start|>system +{{ .System }}<|im_end|> +{{ end }}{{ if .Prompt }}<|im_start|>user +{{ .Prompt }}<|im_end|> +{{ end }}<|im_start|>assistant +{{ .Response }}<|im_end|> +`, + ` +{{- range .Messages }} +{{- if and (eq .Role "user") (isLastMessage $.Messages .) $.System }}<|im_start|>system +{{ $.System }}<|im_end|>{{ print "\n" }} +{{- end }}<|im_start|>{{ .Role }} +{{ .Content }}<|im_end|>{{ print "\n" }} +{{- end }}<|im_start|>assistant +`, + }, + Values{ + Messages: []api.Message{ + {Role: "system", Content: "You are a helpful assistant!"}, + {Role: "user", Content: "Hello friend!"}, + {Role: "assistant", Content: "Hello human!"}, + {Role: "user", Content: "Yay!"}, + }, + }, + `<|im_start|>user +Hello friend!<|im_end|> +<|im_start|>assistant +Hello human!<|im_end|> +<|im_start|>system +You are a helpful assistant!<|im_end|> +<|im_start|>user +Yay!<|im_end|> +<|im_start|>assistant +`, + }, + { + []string{ + `{{ if .Prompt }}Question: {{ .Prompt }} + +{{ end }}Answer: {{ .Response }} + +`, + ` +{{- range .Messages }} +{{- if eq .Role "user" }}Question: {{ .Content }}{{ print "\n\n" }} +{{- else if eq .Role "assistant" }}Answer: {{ .Content }}{{ print "\n\n" }} +{{- end }} +{{- end }}Answer: `, + }, + Values{ + Messages: []api.Message{ + {Role: "user", Content: "What's in this image?", Images: []api.ImageData{[]byte("")}}, + {Role: "assistant", Content: "It's a hot dog."}, + {Role: "user", Content: "What's in _this_ image?"}, + {Role: "user", Images: []api.ImageData{[]byte("")}}, + {Role: "user", Content: "Is it a hot dog?"}, + }, + }, + `Question: [img-0] What's in this image? + +Answer: It's a hot dog. + +Question: What's in _this_ image? + +[img-1] + +Is it a hot dog? + +Answer: `, + }, + } + + for _, tt := range cases { + t.Run("", func(t *testing.T) { + for _, tmpl := range tt.templates { + t.Run("", func(t *testing.T) { + tmpl, err := Parse(tmpl) + if err != nil { + t.Fatal(err) + } + + var b bytes.Buffer + if err := tmpl.Execute(&b, tt.values); err != nil { + t.Fatal(err) + } + + if b.String() != tt.expected { + t.Errorf("expected\n%s,\ngot\n%s", tt.expected, b.String()) + } + }) + } + }) + } +} From 2c3fe1fd972b7810091120f844afc35bc98accbd Mon Sep 17 00:00:00 2001 From: Michael Yang Date: Thu, 20 Jun 2024 11:00:08 -0700 Subject: [PATCH 028/384] comments --- server/prompt.go | 29 +++--- server/prompt_test.go | 34 +++---- server/routes.go | 46 +++++----- template/template.go | 48 +++++----- template/template_test.go | 180 ++++++++++++++++++++++++++++++-------- 5 files changed, 224 insertions(+), 113 deletions(-) diff --git a/server/prompt.go b/server/prompt.go index 5016fbe14..51d691a9f 100644 --- a/server/prompt.go +++ b/server/prompt.go @@ -11,8 +11,13 @@ import ( "github.com/ollama/ollama/template" ) -func chatPrompt(ctx context.Context, r *runnerRef, msgs []api.Message) (prompt string, images []llm.ImageData, _ error) { - // extract system messages which should always be included +type tokenizeFunc func(context.Context, string) ([]int, error) + +// chatPrompt accepts a list of messages and returns the prompt and images that should be used for the next chat turn. +// chatPrompt truncates any messages that exceed the context window of the model, making sure to always include 1) the +// latest message and 2) system messages +func chatPrompt(ctx context.Context, m *Model, tokenize tokenizeFunc, opts *api.Options, msgs []api.Message) (prompt string, images []llm.ImageData, _ error) { + // pull out any system messages which should always be included in the prompt var system []api.Message msgs = slices.DeleteFunc(msgs, func(m api.Message) bool { if m.Role == "system" { @@ -23,32 +28,35 @@ func chatPrompt(ctx context.Context, r *runnerRef, msgs []api.Message) (prompt s return false }) - if len(system) == 0 && r.model.System != "" { + if len(system) == 0 && m.System != "" { // add model system prompt since it wasn't provided - system = append(system, api.Message{Role: "system", Content: r.model.System}) + system = append(system, api.Message{Role: "system", Content: m.System}) } + // always include the last message n := len(msgs) - 1 + // in reverse, find all messages that fit into context window for i := n - 1; i >= 0; i-- { var b bytes.Buffer - if err := r.model.Template.Execute(&b, template.Values{Messages: append(system, msgs[i:]...)}); err != nil { + if err := m.Template.Execute(&b, template.Values{Messages: append(system, msgs[i:]...)}); err != nil { return "", nil, err } - s, err := r.llama.Tokenize(ctx, b.String()) + s, err := tokenize(ctx, b.String()) if err != nil { return "", nil, err } c := len(s) - if r.model.ProjectorPaths != nil { + if m.ProjectorPaths != nil { for _, m := range msgs[i:] { - // TODO: get image embedding length from project metadata + // images are represented as 768 sized embeddings + // TODO: get embedding length from project metadata c += 768 * len(m.Images) } } - if c > r.NumCtx { + if c > opts.NumCtx { slog.Debug("truncating input messages which exceed context length", "truncated", len(msgs[i:])) break } else { @@ -56,8 +64,9 @@ func chatPrompt(ctx context.Context, r *runnerRef, msgs []api.Message) (prompt s } } + // truncate any messages that do not fit into the context window var b bytes.Buffer - if err := r.model.Template.Execute(&b, template.Values{Messages: append(system, msgs[n:]...)}); err != nil { + if err := m.Template.Execute(&b, template.Values{Messages: append(system, msgs[n:]...)}); err != nil { return "", nil, err } diff --git a/server/prompt_test.go b/server/prompt_test.go index 59288b46c..d4cee98c2 100644 --- a/server/prompt_test.go +++ b/server/prompt_test.go @@ -7,15 +7,10 @@ import ( "testing" "github.com/ollama/ollama/api" - "github.com/ollama/ollama/llm" "github.com/ollama/ollama/template" ) -type mock struct { - llm.LlamaServer -} - -func (m mock) Tokenize(_ context.Context, s string) (tokens []int, err error) { +func tokenize(_ context.Context, s string) (tokens []int, err error) { for range strings.Fields(s) { tokens = append(tokens, len(tokens)) } @@ -48,7 +43,7 @@ func TestChatPrompt(t *testing.T) { }, }, { - name: "truncate messages", + name: "truncate messages", limit: 1, msgs: []api.Message{ {Role: "user", Content: "You're a test, Harry!"}, @@ -60,7 +55,7 @@ func TestChatPrompt(t *testing.T) { }, }, { - name: "truncate messages with image", + name: "truncate messages with image", limit: 64, msgs: []api.Message{ {Role: "user", Content: "You're a test, Harry!"}, @@ -75,7 +70,7 @@ func TestChatPrompt(t *testing.T) { }, }, { - name: "truncate messages with images", + name: "truncate messages with images", limit: 64, msgs: []api.Message{ {Role: "user", Content: "You're a test, Harry!", Images: []api.ImageData{[]byte("something")}}, @@ -90,7 +85,7 @@ func TestChatPrompt(t *testing.T) { }, }, { - name: "messages with images", + name: "messages with images", limit: 2048, msgs: []api.Message{ {Role: "user", Content: "You're a test, Harry!", Images: []api.ImageData{[]byte("something")}}, @@ -106,7 +101,7 @@ func TestChatPrompt(t *testing.T) { }, }, { - name: "message with image tag", + name: "message with image tag", limit: 2048, msgs: []api.Message{ {Role: "user", Content: "You're a test, Harry! [img]", Images: []api.ImageData{[]byte("something")}}, @@ -122,7 +117,7 @@ func TestChatPrompt(t *testing.T) { }, }, { - name: "messages with interleaved images", + name: "messages with interleaved images", limit: 2048, msgs: []api.Message{ {Role: "user", Content: "You're a test, Harry!"}, @@ -140,7 +135,7 @@ func TestChatPrompt(t *testing.T) { }, }, { - name: "truncate message with interleaved images", + name: "truncate message with interleaved images", limit: 1024, msgs: []api.Message{ {Role: "user", Content: "You're a test, Harry!"}, @@ -157,7 +152,7 @@ func TestChatPrompt(t *testing.T) { }, }, { - name: "message with system prompt", + name: "message with system prompt", limit: 2048, msgs: []api.Message{ {Role: "system", Content: "You are the Test Who Lived."}, @@ -181,14 +176,9 @@ func TestChatPrompt(t *testing.T) { for _, tt := range cases { t.Run(tt.name, func(t *testing.T) { - r := runnerRef{ - llama: mock{}, - model: &Model{Template: tmpl, ProjectorPaths: []string{"vision"}}, - Options: &api.Options{}, - } - - r.NumCtx = tt.limit - prompt, images, err := chatPrompt(context.TODO(), &r, tt.msgs) + model := Model{Template: tmpl, ProjectorPaths: []string{"vision"}} + opts := api.Options{Runner: api.Runner{NumCtx: tt.limit}} + prompt, images, err := chatPrompt(context.TODO(), &model, tokenize, &opts, tt.msgs) if err != nil { t.Fatal(err) } diff --git a/server/routes.go b/server/routes.go index 35e64511b..1a93e9770 100644 --- a/server/routes.go +++ b/server/routes.go @@ -54,6 +54,8 @@ func init() { gin.SetMode(mode) } +var errRequired = errors.New("is required") + func modelOptions(model *Model, requestOpts map[string]interface{}) (api.Options, error) { opts := api.DefaultOptions() if err := opts.FromMap(model.Options); err != nil { @@ -69,7 +71,7 @@ func modelOptions(model *Model, requestOpts map[string]interface{}) (api.Options func (s *Server) scheduleRunner(ctx context.Context, name string, caps []Capability, requestOpts map[string]any, keepAlive *api.Duration) (*runnerRef, error) { if name == "" { - return nil, errors.New("model is required") + return nil, fmt.Errorf("model %w", errRequired) } model, err := GetModel(name) @@ -121,7 +123,17 @@ func (s *Server) GenerateHandler(c *gin.Context) { c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Sprintf("%q does not support generate", req.Model)}) return } else if err != nil { - handleScheduleError(c, err) + handleScheduleError(c, req.Model, err) + return + } + + if req.Prompt == "" { + c.JSON(http.StatusOK, api.GenerateResponse{ + Model: req.Model, + CreatedAt: time.Now().UTC(), + Done: true, + DoneReason: "load", + }) return } @@ -139,23 +151,11 @@ func (s *Server) GenerateHandler(c *gin.Context) { msgs = append(msgs, api.Message{Role: "system", Content: r.model.System}) } - if req.Prompt != "" { - for _, i := range images { - msgs = append(msgs, api.Message{Role: "user", Content: fmt.Sprintf("[img-%d]", i.ID)}) - } - - msgs = append(msgs, api.Message{Role: "user", Content: req.Prompt}) + for _, i := range images { + msgs = append(msgs, api.Message{Role: "user", Content: fmt.Sprintf("[img-%d]", i.ID)}) } - if len(msgs) == 0 { - c.JSON(http.StatusOK, api.GenerateResponse{ - Model: req.Model, - CreatedAt: time.Now().UTC(), - Done: true, - DoneReason: "load", - }) - return - } + msgs = append(msgs, api.Message{Role: "user", Content: req.Prompt}) tmpl := r.model.Template if req.Template != "" { @@ -256,7 +256,7 @@ func (s *Server) EmbeddingsHandler(c *gin.Context) { r, err := s.scheduleRunner(c.Request.Context(), req.Model, []Capability{}, req.Options, req.KeepAlive) if err != nil { - handleScheduleError(c, err) + handleScheduleError(c, req.Model, err) return } @@ -1135,7 +1135,7 @@ func (s *Server) ChatHandler(c *gin.Context) { c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Sprintf("%q does not support chat", req.Model)}) return } else if err != nil { - handleScheduleError(c, err) + handleScheduleError(c, req.Model, err) return } @@ -1150,7 +1150,7 @@ func (s *Server) ChatHandler(c *gin.Context) { return } - prompt, images, err := chatPrompt(c.Request.Context(), r, req.Messages) + prompt, images, err := chatPrompt(c.Request.Context(), r.model, r.llama.Tokenize, r.Options, req.Messages) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return @@ -1215,12 +1215,16 @@ func (s *Server) ChatHandler(c *gin.Context) { streamResponse(c, ch) } -func handleScheduleError(c *gin.Context, err error) { +func handleScheduleError(c *gin.Context, name string, err error) { switch { + case errors.Is(err, errRequired): + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) case errors.Is(err, context.Canceled): c.JSON(499, gin.H{"error": "request canceled"}) case errors.Is(err, ErrMaxQueue): c.JSON(http.StatusServiceUnavailable, gin.H{"error": err.Error()}) + case errors.Is(err, os.ErrNotExist): + c.JSON(http.StatusNotFound, gin.H{"error": fmt.Sprintf("model %q not found, try pulling it first", name)}) default: c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) } diff --git a/template/template.go b/template/template.go index cfba5a238..c8f8f6d0d 100644 --- a/template/template.go +++ b/template/template.go @@ -83,6 +83,7 @@ type Template struct { raw string } +// response is a template node that can be added to templates that don't already have one var response = parse.ActionNode{ NodeType: parse.NodeAction, Pipe: &parse.PipeNode{ @@ -101,28 +102,25 @@ var response = parse.ActionNode{ }, } +var funcs = template.FuncMap{ + "toJson": func(v any) string { + b, err := json.Marshal(v) + if err != nil { + return "" + } + + return string(b) + }, + "add": func(a, b int) int { + return a + b + }, + "sub": func(a, b int) int { + return a - b + }, +} + func Parse(s string) (*Template, error) { - tmpl := template.New("").Option("missingkey=zero").Funcs(template.FuncMap{ - "toJson": func(v any) string { - b, err := json.Marshal(v) - if err != nil { - return "" - } - - return string(b) - }, - "isLastMessage": func(s []*api.Message, m *api.Message) bool { - for i := len(s) - 1; i >= 0; i-- { - if m.Role != s[i].Role { - continue - } - - return m == s[i] - } - - return false - }, - }) + tmpl := template.New("").Option("missingkey=zero").Funcs(funcs) tmpl, err := tmpl.Parse(s) if err != nil { @@ -218,7 +216,13 @@ func (t *Template) Execute(w io.Writer, v Values) error { return err } -func collate(msgs []api.Message) (system string, collated []*api.Message) { +type messages []*api.Message + +// collate messages based on role. consecutive messages of the same role are merged +// into a single message. collate also pulls out and merges messages with Role == "system" +// which are templated separately. As a side effect, it mangles message content adding image +// tags ([img-%d]) as needed +func collate(msgs []api.Message) (system string, collated messages) { var n int for i := range msgs { msg := msgs[i] diff --git a/template/template_test.go b/template/template_test.go index 5d5dad4b2..ac92bf489 100644 --- a/template/template_test.go +++ b/template/template_test.go @@ -8,6 +8,7 @@ import ( "os" "path/filepath" "slices" + "strconv" "testing" "text/template" @@ -15,6 +16,98 @@ import ( "github.com/ollama/ollama/llm" ) +func TestFuncs(t *testing.T) { + t.Run("toJson", func(t *testing.T) { + cases := []struct { + input any + expected string + }{ + {nil, "null"}, + {true, "true"}, + {false, "false"}, + {0, "0"}, + {1, "1"}, + {1.0, "1"}, + {1.1, "1.1"}, + {"", `""`}, + {"hello", `"hello"`}, + {[]int{1, 2, 3}, "[1,2,3]"}, + {[]string{"a", "b", "c"}, `["a","b","c"]`}, + {map[string]int{"a": 1, "b": 2}, `{"a":1,"b":2}`}, + {map[string]string{"a": "b", "c": "d"}, `{"a":"b","c":"d"}`}, + } + + for _, tt := range cases { + t.Run(tt.expected, func(t *testing.T) { + toJson, ok := funcs["toJson"].(func(any) string) + if !ok { + t.Fatal("toJson is not a function") + } + + if s := toJson(tt.input); s != tt.expected { + t.Errorf("expected %q, got %q", tt.expected, s) + } + }) + } + }) + + t.Run("add", func(t *testing.T) { + cases := []struct { + a, b int + expected int + }{ + {0, 0, 0}, + {0, 1, 1}, + {1, 0, 1}, + {1, 1, 2}, + {1, -1, 0}, + {-1, 1, 0}, + {-1, -1, -2}, + } + + for _, tt := range cases { + t.Run(strconv.Itoa(tt.expected), func(t *testing.T) { + add, ok := funcs["add"].(func(int, int) int) + if !ok { + t.Fatal("add is not a function") + } + + if n := add(tt.a, tt.b); n != tt.expected { + t.Errorf("expected %d, got %d", tt.expected, n) + } + }) + } + }) + + t.Run("sub", func(t *testing.T) { + cases := []struct { + a, b int + expected int + }{ + {0, 0, 0}, + {0, 1, -1}, + {1, 0, 1}, + {1, 1, 0}, + {1, -1, 2}, + {-1, 1, -2}, + {-1, -1, 0}, + } + + for _, tt := range cases { + t.Run(strconv.Itoa(tt.expected), func(t *testing.T) { + sub, ok := funcs["sub"].(func(int, int) int) + if !ok { + t.Fatal("sub is not a function") + } + + if n := sub(tt.a, tt.b); n != tt.expected { + t.Errorf("expected %d, got %d", tt.expected, n) + } + }) + } + }) +} + func TestNamed(t *testing.T) { f, err := os.Open(filepath.Join("testdata", "templates.jsonl")) if err != nil { @@ -89,77 +182,86 @@ func TestParse(t *testing.T) { } func TestExecuteWithMessages(t *testing.T) { + type template struct { + name string + template string + } cases := []struct { - templates []string + name string + templates []template values Values expected string }{ { - []string{ - `[INST] {{ if .System }}{{ .System }}{{ print "\n\n" }}{{ end }}{{ .Prompt }}[/INST] `, - `[INST] {{ if .System }}{{ .System }}{{ print "\n\n" }}{{ end }}{{ .Prompt }}[/INST] {{ .Response }}`, - `{{- range .Messages }} -{{- if eq .Role "user" }}[INST] {{ if and (isLastMessage $.Messages .) $.System }}{{ $.System }}{{ print "\n\n" }} + "mistral", + []template{ + {"no response", `[INST] {{ if .System }}{{ .System }}{{ "\n\n" }}{{ end }}{{ .Prompt }}[/INST] `}, + {"response", `[INST] {{ if .System }}{{ .System }}{{ "\n\n" }}{{ end }}{{ .Prompt }}[/INST] {{ .Response }}`}, + {"messages", `{{- range .Messages }} +{{- if eq .Role "user" }}[INST] {{ if and (eq (index $.Messages (sub (len $.Messages) 1)) .) $.System }}{{ $.System }}{{ "\n\n" }} {{- end }}{{ .Content }}[/INST] {{ else if eq .Role "assistant" }}{{ .Content }} {{- end }} -{{- end }}`, +{{- end }}`}, }, Values{ Messages: []api.Message{ {Role: "user", Content: "Hello friend!"}, {Role: "assistant", Content: "Hello human!"}, - {Role: "user", Content: "Yay!"}, + {Role: "user", Content: "What is your name?"}, }, }, - `[INST] Hello friend![/INST] Hello human![INST] Yay![/INST] `, + `[INST] Hello friend![/INST] Hello human![INST] What is your name?[/INST] `, }, { - []string{ - `[INST] {{ if .System }}{{ .System }}{{ print "\n\n" }}{{ end }}{{ .Prompt }}[/INST] `, - `[INST] {{ if .System }}{{ .System }}{{ print "\n\n" }}{{ end }}{{ .Prompt }}[/INST] {{ .Response }}`, - ` + "mistral system", + []template{ + {"no response", `[INST] {{ if .System }}{{ .System }}{{ "\n\n" }}{{ end }}{{ .Prompt }}[/INST] `}, + {"response", `[INST] {{ if .System }}{{ .System }}{{ "\n\n" }}{{ end }}{{ .Prompt }}[/INST] {{ .Response }}`}, + {"messages", ` {{- range .Messages }} -{{- if eq .Role "user" }}[INST] {{ if and (isLastMessage $.Messages .) $.System }}{{ $.System }}{{ print "\n\n" }} +{{- if eq .Role "user" }}[INST] {{ if and (eq (index $.Messages (sub (len $.Messages) 1)) .) $.System }}{{ $.System }}{{ "\n\n" }} {{- end }}{{ .Content }}[/INST] {{ else if eq .Role "assistant" }}{{ .Content }} {{- end }} -{{- end }}`, +{{- end }}`}, }, Values{ Messages: []api.Message{ {Role: "system", Content: "You are a helpful assistant!"}, {Role: "user", Content: "Hello friend!"}, {Role: "assistant", Content: "Hello human!"}, - {Role: "user", Content: "Yay!"}, + {Role: "user", Content: "What is your name?"}, }, }, `[INST] Hello friend![/INST] Hello human![INST] You are a helpful assistant! -Yay![/INST] `, +What is your name?[/INST] `, }, { - []string{ - `{{ if .System }}<|im_start|>system + "chatml", + []template{ + // this does not have a "no response" test because it's impossible to render the same output + {"response", `{{ if .System }}<|im_start|>system {{ .System }}<|im_end|> {{ end }}{{ if .Prompt }}<|im_start|>user {{ .Prompt }}<|im_end|> {{ end }}<|im_start|>assistant {{ .Response }}<|im_end|> -`, - ` +`}, + {"messages", ` {{- range .Messages }} -{{- if and (eq .Role "user") (isLastMessage $.Messages .) $.System }}<|im_start|>system -{{ $.System }}<|im_end|>{{ print "\n" }} +{{- if and (eq .Role "user") (eq (index $.Messages (sub (len $.Messages) 1)) .) $.System }}<|im_start|>system +{{ $.System }}<|im_end|>{{ "\n" }} {{- end }}<|im_start|>{{ .Role }} -{{ .Content }}<|im_end|>{{ print "\n" }} +{{ .Content }}<|im_end|>{{ "\n" }} {{- end }}<|im_start|>assistant -`, +`}, }, Values{ Messages: []api.Message{ {Role: "system", Content: "You are a helpful assistant!"}, {Role: "user", Content: "Hello friend!"}, {Role: "assistant", Content: "Hello human!"}, - {Role: "user", Content: "Yay!"}, + {Role: "user", Content: "What is your name?"}, }, }, `<|im_start|>user @@ -169,23 +271,25 @@ Hello human!<|im_end|> <|im_start|>system You are a helpful assistant!<|im_end|> <|im_start|>user -Yay!<|im_end|> +What is your name?<|im_end|> <|im_start|>assistant `, }, { - []string{ - `{{ if .Prompt }}Question: {{ .Prompt }} + "moondream", + []template{ + // this does not have a "no response" test because it's impossible to render the same output + {"response", `{{ if .Prompt }}Question: {{ .Prompt }} {{ end }}Answer: {{ .Response }} -`, - ` +`}, + {"messages", ` {{- range .Messages }} -{{- if eq .Role "user" }}Question: {{ .Content }}{{ print "\n\n" }} -{{- else if eq .Role "assistant" }}Answer: {{ .Content }}{{ print "\n\n" }} +{{- if eq .Role "user" }}Question: {{ .Content }}{{ "\n\n" }} +{{- else if eq .Role "assistant" }}Answer: {{ .Content }}{{ "\n\n" }} {{- end }} -{{- end }}Answer: `, +{{- end }}Answer: `}, }, Values{ Messages: []api.Message{ @@ -211,10 +315,10 @@ Answer: `, } for _, tt := range cases { - t.Run("", func(t *testing.T) { - for _, tmpl := range tt.templates { - t.Run("", func(t *testing.T) { - tmpl, err := Parse(tmpl) + t.Run(tt.name, func(t *testing.T) { + for _, ttt := range tt.templates { + t.Run(ttt.name, func(t *testing.T) { + tmpl, err := Parse(ttt.template) if err != nil { t.Fatal(err) } From ac7a842e550721fbc00e36e416e7cf6606993149 Mon Sep 17 00:00:00 2001 From: Michael Yang Date: Wed, 3 Jul 2024 09:00:07 -0700 Subject: [PATCH 029/384] fix model reloading ensure runtime model changes (template, system prompt, messages, options) are captured on model updates without needing to reload the server --- llm/server.go | 2 +- server/routes.go | 42 ++++++++++++++++++++++-------------------- 2 files changed, 23 insertions(+), 21 deletions(-) diff --git a/llm/server.go b/llm/server.go index 206f9e391..229d61e4a 100644 --- a/llm/server.go +++ b/llm/server.go @@ -679,7 +679,7 @@ type CompletionRequest struct { Prompt string Format string Images []ImageData - Options api.Options + Options *api.Options } type CompletionResponse struct { diff --git a/server/routes.go b/server/routes.go index 1a93e9770..4059c7c52 100644 --- a/server/routes.go +++ b/server/routes.go @@ -69,23 +69,25 @@ func modelOptions(model *Model, requestOpts map[string]interface{}) (api.Options return opts, nil } -func (s *Server) scheduleRunner(ctx context.Context, name string, caps []Capability, requestOpts map[string]any, keepAlive *api.Duration) (*runnerRef, error) { +// scheduleRunner schedules a runner after validating inputs such as capabilities and model options. +// It returns the allocated runner, model instance, and consolidated options if successful and error otherwise. +func (s *Server) scheduleRunner(ctx context.Context, name string, caps []Capability, requestOpts map[string]any, keepAlive *api.Duration) (llm.LlamaServer, *Model, *api.Options, error) { if name == "" { - return nil, fmt.Errorf("model %w", errRequired) + return nil, nil, nil, fmt.Errorf("model %w", errRequired) } model, err := GetModel(name) if err != nil { - return nil, err + return nil, nil, nil, err } if err := model.CheckCapabilities(caps...); err != nil { - return nil, fmt.Errorf("%s %w", name, err) + return nil, nil, nil, fmt.Errorf("%s %w", name, err) } opts, err := modelOptions(model, requestOpts) if err != nil { - return nil, err + return nil, nil, nil, err } runnerCh, errCh := s.sched.GetRunner(ctx, model, opts, keepAlive) @@ -93,10 +95,10 @@ func (s *Server) scheduleRunner(ctx context.Context, name string, caps []Capabil select { case runner = <-runnerCh: case err = <-errCh: - return nil, err + return nil, nil, nil, err } - return runner, nil + return runner.llama, model, &opts, nil } func (s *Server) GenerateHandler(c *gin.Context) { @@ -118,7 +120,7 @@ func (s *Server) GenerateHandler(c *gin.Context) { } caps := []Capability{CapabilityCompletion} - r, err := s.scheduleRunner(c.Request.Context(), req.Model, caps, req.Options, req.KeepAlive) + r, m, opts, err := s.scheduleRunner(c.Request.Context(), req.Model, caps, req.Options, req.KeepAlive) if errors.Is(err, errCapabilityCompletion) { c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Sprintf("%q does not support generate", req.Model)}) return @@ -147,8 +149,8 @@ func (s *Server) GenerateHandler(c *gin.Context) { var msgs []api.Message if req.System != "" { msgs = append(msgs, api.Message{Role: "system", Content: req.System}) - } else if r.model.System != "" { - msgs = append(msgs, api.Message{Role: "system", Content: r.model.System}) + } else if m.System != "" { + msgs = append(msgs, api.Message{Role: "system", Content: m.System}) } for _, i := range images { @@ -157,7 +159,7 @@ func (s *Server) GenerateHandler(c *gin.Context) { msgs = append(msgs, api.Message{Role: "user", Content: req.Prompt}) - tmpl := r.model.Template + tmpl := m.Template if req.Template != "" { tmpl, err = template.Parse(req.Template) if err != nil { @@ -168,7 +170,7 @@ func (s *Server) GenerateHandler(c *gin.Context) { var b bytes.Buffer if req.Context != nil { - s, err := r.llama.Detokenize(c.Request.Context(), req.Context) + s, err := r.Detokenize(c.Request.Context(), req.Context) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return @@ -190,11 +192,11 @@ func (s *Server) GenerateHandler(c *gin.Context) { ch := make(chan any) go func() { defer close(ch) - if err := r.llama.Completion(c.Request.Context(), llm.CompletionRequest{ + if err := r.Completion(c.Request.Context(), llm.CompletionRequest{ Prompt: prompt, Images: images, Format: req.Format, - Options: *r.Options, + Options: opts, }, func(r llm.CompletionResponse) { ch <- api.GenerateResponse{ Model: req.Model, @@ -254,7 +256,7 @@ func (s *Server) EmbeddingsHandler(c *gin.Context) { return } - r, err := s.scheduleRunner(c.Request.Context(), req.Model, []Capability{}, req.Options, req.KeepAlive) + r, _, _, err := s.scheduleRunner(c.Request.Context(), req.Model, []Capability{}, req.Options, req.KeepAlive) if err != nil { handleScheduleError(c, req.Model, err) return @@ -266,7 +268,7 @@ func (s *Server) EmbeddingsHandler(c *gin.Context) { return } - embedding, err := r.llama.Embedding(c.Request.Context(), req.Prompt) + embedding, err := r.Embedding(c.Request.Context(), req.Prompt) if err != nil { slog.Info(fmt.Sprintf("embedding generation failed: %v", err)) c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to generate embedding"}) @@ -1130,7 +1132,7 @@ func (s *Server) ChatHandler(c *gin.Context) { } caps := []Capability{CapabilityCompletion} - r, err := s.scheduleRunner(c.Request.Context(), req.Model, caps, req.Options, req.KeepAlive) + r, m, opts, err := s.scheduleRunner(c.Request.Context(), req.Model, caps, req.Options, req.KeepAlive) if errors.Is(err, errCapabilityCompletion) { c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Sprintf("%q does not support chat", req.Model)}) return @@ -1150,7 +1152,7 @@ func (s *Server) ChatHandler(c *gin.Context) { return } - prompt, images, err := chatPrompt(c.Request.Context(), r.model, r.llama.Tokenize, r.Options, req.Messages) + prompt, images, err := chatPrompt(c.Request.Context(), m, r.Tokenize, opts, req.Messages) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return @@ -1161,11 +1163,11 @@ func (s *Server) ChatHandler(c *gin.Context) { ch := make(chan any) go func() { defer close(ch) - if err := r.llama.Completion(c.Request.Context(), llm.CompletionRequest{ + if err := r.Completion(c.Request.Context(), llm.CompletionRequest{ Prompt: prompt, Images: images, Format: req.Format, - Options: *r.Options, + Options: opts, }, func(r llm.CompletionResponse) { ch <- api.ChatResponse{ Model: req.Model, From 326363b3a72d9e2972a019dfc4c6147ea901f501 Mon Sep 17 00:00:00 2001 From: Michael Yang Date: Wed, 3 Jul 2024 13:49:14 -0700 Subject: [PATCH 030/384] no funcs --- template/template.go | 19 +------ template/template_test.go | 105 +++----------------------------------- 2 files changed, 7 insertions(+), 117 deletions(-) diff --git a/template/template.go b/template/template.go index c8f8f6d0d..b133b97e9 100644 --- a/template/template.go +++ b/template/template.go @@ -102,25 +102,8 @@ var response = parse.ActionNode{ }, } -var funcs = template.FuncMap{ - "toJson": func(v any) string { - b, err := json.Marshal(v) - if err != nil { - return "" - } - - return string(b) - }, - "add": func(a, b int) int { - return a + b - }, - "sub": func(a, b int) int { - return a - b - }, -} - func Parse(s string) (*Template, error) { - tmpl := template.New("").Option("missingkey=zero").Funcs(funcs) + tmpl := template.New("").Option("missingkey=zero") tmpl, err := tmpl.Parse(s) if err != nil { diff --git a/template/template_test.go b/template/template_test.go index ac92bf489..ac16bd606 100644 --- a/template/template_test.go +++ b/template/template_test.go @@ -8,7 +8,6 @@ import ( "os" "path/filepath" "slices" - "strconv" "testing" "text/template" @@ -16,98 +15,6 @@ import ( "github.com/ollama/ollama/llm" ) -func TestFuncs(t *testing.T) { - t.Run("toJson", func(t *testing.T) { - cases := []struct { - input any - expected string - }{ - {nil, "null"}, - {true, "true"}, - {false, "false"}, - {0, "0"}, - {1, "1"}, - {1.0, "1"}, - {1.1, "1.1"}, - {"", `""`}, - {"hello", `"hello"`}, - {[]int{1, 2, 3}, "[1,2,3]"}, - {[]string{"a", "b", "c"}, `["a","b","c"]`}, - {map[string]int{"a": 1, "b": 2}, `{"a":1,"b":2}`}, - {map[string]string{"a": "b", "c": "d"}, `{"a":"b","c":"d"}`}, - } - - for _, tt := range cases { - t.Run(tt.expected, func(t *testing.T) { - toJson, ok := funcs["toJson"].(func(any) string) - if !ok { - t.Fatal("toJson is not a function") - } - - if s := toJson(tt.input); s != tt.expected { - t.Errorf("expected %q, got %q", tt.expected, s) - } - }) - } - }) - - t.Run("add", func(t *testing.T) { - cases := []struct { - a, b int - expected int - }{ - {0, 0, 0}, - {0, 1, 1}, - {1, 0, 1}, - {1, 1, 2}, - {1, -1, 0}, - {-1, 1, 0}, - {-1, -1, -2}, - } - - for _, tt := range cases { - t.Run(strconv.Itoa(tt.expected), func(t *testing.T) { - add, ok := funcs["add"].(func(int, int) int) - if !ok { - t.Fatal("add is not a function") - } - - if n := add(tt.a, tt.b); n != tt.expected { - t.Errorf("expected %d, got %d", tt.expected, n) - } - }) - } - }) - - t.Run("sub", func(t *testing.T) { - cases := []struct { - a, b int - expected int - }{ - {0, 0, 0}, - {0, 1, -1}, - {1, 0, 1}, - {1, 1, 0}, - {1, -1, 2}, - {-1, 1, -2}, - {-1, -1, 0}, - } - - for _, tt := range cases { - t.Run(strconv.Itoa(tt.expected), func(t *testing.T) { - sub, ok := funcs["sub"].(func(int, int) int) - if !ok { - t.Fatal("sub is not a function") - } - - if n := sub(tt.a, tt.b); n != tt.expected { - t.Errorf("expected %d, got %d", tt.expected, n) - } - }) - } - }) -} - func TestNamed(t *testing.T) { f, err := os.Open(filepath.Join("testdata", "templates.jsonl")) if err != nil { @@ -197,8 +104,8 @@ func TestExecuteWithMessages(t *testing.T) { []template{ {"no response", `[INST] {{ if .System }}{{ .System }}{{ "\n\n" }}{{ end }}{{ .Prompt }}[/INST] `}, {"response", `[INST] {{ if .System }}{{ .System }}{{ "\n\n" }}{{ end }}{{ .Prompt }}[/INST] {{ .Response }}`}, - {"messages", `{{- range .Messages }} -{{- if eq .Role "user" }}[INST] {{ if and (eq (index $.Messages (sub (len $.Messages) 1)) .) $.System }}{{ $.System }}{{ "\n\n" }} + {"messages", `{{- range $index, $_ := .Messages }} +{{- if eq .Role "user" }}[INST] {{ if and (eq (len (slice $.Messages $index)) 1) $.System }}{{ $.System }}{{ "\n\n" }} {{- end }}{{ .Content }}[/INST] {{ else if eq .Role "assistant" }}{{ .Content }} {{- end }} {{- end }}`}, @@ -218,8 +125,8 @@ func TestExecuteWithMessages(t *testing.T) { {"no response", `[INST] {{ if .System }}{{ .System }}{{ "\n\n" }}{{ end }}{{ .Prompt }}[/INST] `}, {"response", `[INST] {{ if .System }}{{ .System }}{{ "\n\n" }}{{ end }}{{ .Prompt }}[/INST] {{ .Response }}`}, {"messages", ` -{{- range .Messages }} -{{- if eq .Role "user" }}[INST] {{ if and (eq (index $.Messages (sub (len $.Messages) 1)) .) $.System }}{{ $.System }}{{ "\n\n" }} +{{- range $index, $_ := .Messages }} +{{- if eq .Role "user" }}[INST] {{ if and (eq (len (slice $.Messages $index)) 1) $.System }}{{ $.System }}{{ "\n\n" }} {{- end }}{{ .Content }}[/INST] {{ else if eq .Role "assistant" }}{{ .Content }} {{- end }} {{- end }}`}, @@ -248,8 +155,8 @@ What is your name?[/INST] `, {{ .Response }}<|im_end|> `}, {"messages", ` -{{- range .Messages }} -{{- if and (eq .Role "user") (eq (index $.Messages (sub (len $.Messages) 1)) .) $.System }}<|im_start|>system +{{- range $index, $_ := .Messages }} +{{- if and (eq .Role "user") (eq (len (slice $.Messages $index)) 1) $.System }}<|im_start|>system {{ $.System }}<|im_end|>{{ "\n" }} {{- end }}<|im_start|>{{ .Role }} {{ .Content }}<|im_end|>{{ "\n" }} From 631cfd9e62362d6aea72da96fa67c52a1fd4e990 Mon Sep 17 00:00:00 2001 From: Blake Mizerany Date: Fri, 5 Jul 2024 13:42:30 -0700 Subject: [PATCH 031/384] types/model: remove knowledge of digest (#5500) This was leading to ambiguity and confusion in ollama.com, and is not used anywhere in ollama at the moment. Once manifests are addressable by digest, we can add this back in, and in a way that is more tailored to the concept of addressing a manifest by digest. --- types/model/name.go | 22 +++++++--------------- types/model/name_test.go | 34 +++++++--------------------------- 2 files changed, 14 insertions(+), 42 deletions(-) diff --git a/types/model/name.go b/types/model/name.go index e645a844c..5e475687e 100644 --- a/types/model/name.go +++ b/types/model/name.go @@ -91,7 +91,6 @@ type Name struct { Namespace string Model string Tag string - RawDigest string } // ParseName parses and assembles a Name from a name string. The @@ -143,11 +142,6 @@ func ParseNameBare(s string) Name { var n Name var promised bool - s, n.RawDigest, promised = cutLast(s, "@") - if promised && n.RawDigest == "" { - n.RawDigest = MissingPart - } - // "/" is an illegal tag character, so we can use it to split the host if strings.LastIndex(s, ":") > strings.LastIndex(s, "/") { s, n.Tag, _ = cutPromised(s, ":") @@ -222,10 +216,6 @@ func (n Name) String() string { b.WriteByte(':') b.WriteString(n.Tag) } - if n.RawDigest != "" { - b.WriteByte('@') - b.WriteString(n.RawDigest) - } return b.String() } @@ -250,16 +240,18 @@ func (n Name) DisplayShortest() string { return sb.String() } -func IsValidNamespace(namespace string) bool { - return isValidPart(kindNamespace, namespace) +// IsValidNamespace reports whether the provided string is a valid +// namespace. +func IsValidNamespace(s string) bool { + return isValidPart(kindNamespace, s) } // IsValid reports whether all parts of the name are present and valid. The // digest is a special case, and is checked for validity only if present. +// +// Note: The digest check has been removed as is planned to be added back in +// at a later time. func (n Name) IsValid() bool { - if n.RawDigest != "" && !isValidPart(kindDigest, n.RawDigest) { - return false - } return n.IsFullyQualified() } diff --git a/types/model/name_test.go b/types/model/name_test.go index 008dd586c..794d14d79 100644 --- a/types/model/name_test.go +++ b/types/model/name_test.go @@ -122,21 +122,6 @@ func TestParseNameParts(t *testing.T) { }, wantFilepath: filepath.Join(part350, part80, part80, part80), }, - { - in: "@digest", - want: Name{ - RawDigest: "digest", - }, - wantValidDigest: false, - }, - { - in: "model@sha256:123", - want: Name{ - Model: "model", - RawDigest: "sha256:123", - }, - wantValidDigest: true, - }, } for _, tt := range cases { @@ -160,22 +145,18 @@ var testCases = map[string]bool{ // name -> valid "_why/_the/_lucky:_stiff": true, // minimal - "h/n/m:t@d": true, + "h/n/m:t": true, "host/namespace/model:tag": true, "host/namespace/model": false, "namespace/model": false, "model": false, - "@sha256-1000000000000000000000000000000000000000000000000000000000000000": false, - "model@sha256-1000000000000000000000000000000000000000000000000000000000000000": false, - "model@sha256:1000000000000000000000000000000000000000000000000000000000000000": false, // long (but valid) part80 + "/" + part80 + "/" + part80 + ":" + part80: true, part350 + "/" + part80 + "/" + part80 + ":" + part80: true, - "h/nn/mm:t@sha256-1000000000000000000000000000000000000000000000000000000000000000": true, // bare minimum part sizes - "h/nn/mm:t@sha256:1000000000000000000000000000000000000000000000000000000000000000": true, // bare minimum part sizes + "h/nn/mm:t": true, // bare minimum part sizes // unqualified "m": false, @@ -196,11 +177,10 @@ var testCases = map[string]bool{ // name -> valid "@": false, // not starting with alphanum - "-hh/nn/mm:tt@dd": false, - "hh/-nn/mm:tt@dd": false, - "hh/nn/-mm:tt@dd": false, - "hh/nn/mm:-tt@dd": false, - "hh/nn/mm:tt@-dd": false, + "-hh/nn/mm:tt": false, + "hh/-nn/mm:tt": false, + "hh/nn/-mm:tt": false, + "hh/nn/mm:-tt": false, // hosts "host:https/namespace/model:tag": true, @@ -334,7 +314,7 @@ func FuzzName(f *testing.F) { f.Fuzz(func(t *testing.T, s string) { n := ParseNameBare(s) if n.IsValid() { - parts := [...]string{n.Host, n.Namespace, n.Model, n.Tag, n.RawDigest} + parts := [...]string{n.Host, n.Namespace, n.Model, n.Tag} for _, part := range parts { if part == ".." { t.Errorf("unexpected .. as valid part") From 9d30f9f8b3836e8d617eadf63a71d8363ff56c7e Mon Sep 17 00:00:00 2001 From: Daniel Hiltgen Date: Fri, 5 Jul 2024 12:25:53 -0700 Subject: [PATCH 032/384] Always go build in CI generate steps With the recent cgo changes, bugs can sneak through if we don't make sure to `go build` all the permutations --- .github/workflows/test.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 29adf56f3..13d1c957c 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -58,6 +58,7 @@ jobs: runs-on: ${{ matrix.os }} env: GOARCH: ${{ matrix.arch }} + CGO_ENABLED: '1' steps: - uses: actions/checkout@v4 - uses: actions/setup-go@v5 @@ -79,6 +80,7 @@ jobs: - run: go generate -x ./... if: ${{ ! startsWith(matrix.os, 'windows-') }} name: 'Unix Go Generate' + - run: go build . - uses: actions/upload-artifact@v4 with: name: ${{ matrix.os }}-${{ matrix.arch }}-libraries From 4fd5f3526a116d05cd74cfcc7217d4e6326e1bea Mon Sep 17 00:00:00 2001 From: Jeffrey Morgan Date: Fri, 5 Jul 2024 19:07:01 -0400 Subject: [PATCH 033/384] fix cmake build (#5505) --- llm/ext_server/CMakeLists.txt | 29 ++++++++++++++++------------- llm/generate/gen_common.sh | 1 + 2 files changed, 17 insertions(+), 13 deletions(-) diff --git a/llm/ext_server/CMakeLists.txt b/llm/ext_server/CMakeLists.txt index 9de50739c..c300244f9 100644 --- a/llm/ext_server/CMakeLists.txt +++ b/llm/ext_server/CMakeLists.txt @@ -1,14 +1,17 @@ - -set(TARGET ollama_llama_server) -option(LLAMA_SERVER_VERBOSE "Build verbose logging option for Server" ON) -include_directories(${CMAKE_CURRENT_SOURCE_DIR}) -add_executable(${TARGET} server.cpp utils.hpp json.hpp httplib.h) -install(TARGETS ${TARGET} RUNTIME) -target_compile_definitions(${TARGET} PRIVATE - SERVER_VERBOSE=$ -) -target_link_libraries(${TARGET} PRIVATE ggml llama common llava ${CMAKE_THREAD_LIBS_INIT}) -if (WIN32) - TARGET_LINK_LIBRARIES(${TARGET} PRIVATE ws2_32) -endif() + +set(TARGET ollama_llama_server) +option(LLAMA_SERVER_VERBOSE "Build verbose logging option for Server" ON) +include_directories(${CMAKE_CURRENT_SOURCE_DIR}) +add_executable(${TARGET} server.cpp utils.hpp json.hpp httplib.h) +target_compile_definitions(${TARGET} PRIVATE + SERVER_VERBOSE=$ +) +target_link_libraries(${TARGET} PRIVATE ggml llama common llava ${CMAKE_THREAD_LIBS_INIT}) +install(TARGETS ollama_llama_server ggml llama + RUNTIME DESTINATION "${CMAKE_BINARY_DIR}/bin" + LIBRARY DESTINATION "${CMAKE_BINARY_DIR}/bin" + COMPONENT ollama_llama_server) +if (WIN32) + TARGET_LINK_LIBRARIES(${TARGET} PRIVATE ws2_32) +endif() target_compile_features(${TARGET} PRIVATE cxx_std_11) \ No newline at end of file diff --git a/llm/generate/gen_common.sh b/llm/generate/gen_common.sh index da1b06882..23feaf99d 100644 --- a/llm/generate/gen_common.sh +++ b/llm/generate/gen_common.sh @@ -81,6 +81,7 @@ apply_patches() { build() { cmake -S ${LLAMACPP_DIR} -B ${BUILD_DIR} ${CMAKE_DEFS} cmake --build ${BUILD_DIR} ${CMAKE_TARGETS} -j8 + cmake --install ${BUILD_DIR} --component ollama_llama_server } compress() { From fb6cbc02fbe0ff8d791413a81558a1fe9725b778 Mon Sep 17 00:00:00 2001 From: Michael Yang Date: Thu, 27 Jun 2024 14:15:17 -0700 Subject: [PATCH 034/384] update named templates --- go.mod | 3 +- server/routes_create_test.go | 4 +- template/alfred.gotmpl | 9 ++- template/alpaca.gotmpl | 14 +++- template/chatml.gotmpl | 11 ++- template/chatqa.gotmpl | 14 +++- template/codellama-70b-instruct.gotmpl | 13 +++- template/falcon-instruct.gotmpl | 12 +++- template/gemma-instruct.gotmpl | 14 +++- template/granite-instruct.gotmpl | 16 ++++- template/llama2-chat.gotmpl | 15 +++- template/llama3-instruct.gotmpl | 14 +++- template/magicoder.gotmpl | 15 +++- template/mistral-instruct.gotmpl | 15 ++-- template/openchat.gotmpl | 12 +++- template/phi-3.gotmpl | 11 ++- template/solar-instruct.gotmpl | 16 ++++- template/starcoder2-instruct.gotmpl | 15 ++++ template/template_test.go | 69 ++++++++++++++++++- .../alfred.gotmpl/system-user-assistant-user | 1 + template/testdata/alfred.gotmpl/user | 1 + .../alfred.gotmpl/user-assistant-user | 1 + .../alpaca.gotmpl/system-user-assistant-user | 10 +++ template/testdata/alpaca.gotmpl/user | 4 ++ .../alpaca.gotmpl/user-assistant-user | 10 +++ .../chatml.gotmpl/system-user-assistant-user | 9 +++ template/testdata/chatml.gotmpl/user | 3 + .../chatml.gotmpl/user-assistant-user | 7 ++ .../chatqa.gotmpl/system-user-assistant-user | 9 +++ template/testdata/chatqa.gotmpl/user | 3 + .../chatqa.gotmpl/user-assistant-user | 7 ++ .../system-user-assistant-user | 11 +++ .../codellama-70b-instruct.gotmpl/user | 5 ++ .../user-assistant-user | 9 +++ .../system-user-assistant-user | 8 +++ template/testdata/falcon-instruct.gotmpl/user | 3 + .../user-assistant-user | 7 ++ .../system-user-assistant-user | 8 +++ template/testdata/gemma-instruct.gotmpl/user | 3 + .../gemma-instruct.gotmpl/user-assistant-user | 7 ++ .../system-user-assistant-user | 13 ++++ .../testdata/granite-instruct.gotmpl/user | 4 ++ .../user-assistant-user | 10 +++ .../system-user-assistant-user | 5 ++ template/testdata/llama2-chat.gotmpl/user | 3 + .../llama2-chat.gotmpl/user-assistant-user | 3 + .../system-user-assistant-user | 10 +++ template/testdata/llama3-instruct.gotmpl/user | 4 ++ .../user-assistant-user | 8 +++ .../system-user-assistant-user | 12 ++++ template/testdata/magicoder.gotmpl/user | 4 ++ .../magicoder.gotmpl/user-assistant-user | 10 +++ .../system-user-assistant-user | 2 + .../testdata/mistral-instruct.gotmpl/user | 1 + .../user-assistant-user | 1 + .../system-user-assistant-user | 1 + template/testdata/openchat.gotmpl/user | 1 + .../openchat.gotmpl/user-assistant-user | 1 + .../phi-3.gotmpl/system-user-assistant-user | 9 +++ template/testdata/phi-3.gotmpl/user | 3 + .../testdata/phi-3.gotmpl/user-assistant-user | 7 ++ .../system-user-assistant-user | 13 ++++ template/testdata/solar-instruct.gotmpl/user | 4 ++ .../solar-instruct.gotmpl/user-assistant-user | 10 +++ .../system-user-assistant-user | 12 ++++ .../testdata/starcoder2-instruct.gotmpl/user | 4 ++ .../user-assistant-user | 10 +++ .../vicuna.gotmpl/system-user-assistant-user | 6 ++ template/testdata/vicuna.gotmpl/user | 2 + .../vicuna.gotmpl/user-assistant-user | 4 ++ .../zephyr.gotmpl/system-user-assistant-user | 9 +++ template/testdata/zephyr.gotmpl/user | 3 + .../zephyr.gotmpl/user-assistant-user | 7 ++ template/vicuna.gotmpl | 13 +++- template/zephyr.gotmpl | 11 ++- 75 files changed, 611 insertions(+), 27 deletions(-) create mode 100644 template/testdata/alfred.gotmpl/system-user-assistant-user create mode 100644 template/testdata/alfred.gotmpl/user create mode 100644 template/testdata/alfred.gotmpl/user-assistant-user create mode 100644 template/testdata/alpaca.gotmpl/system-user-assistant-user create mode 100644 template/testdata/alpaca.gotmpl/user create mode 100644 template/testdata/alpaca.gotmpl/user-assistant-user create mode 100644 template/testdata/chatml.gotmpl/system-user-assistant-user create mode 100644 template/testdata/chatml.gotmpl/user create mode 100644 template/testdata/chatml.gotmpl/user-assistant-user create mode 100644 template/testdata/chatqa.gotmpl/system-user-assistant-user create mode 100644 template/testdata/chatqa.gotmpl/user create mode 100644 template/testdata/chatqa.gotmpl/user-assistant-user create mode 100644 template/testdata/codellama-70b-instruct.gotmpl/system-user-assistant-user create mode 100644 template/testdata/codellama-70b-instruct.gotmpl/user create mode 100644 template/testdata/codellama-70b-instruct.gotmpl/user-assistant-user create mode 100644 template/testdata/falcon-instruct.gotmpl/system-user-assistant-user create mode 100644 template/testdata/falcon-instruct.gotmpl/user create mode 100644 template/testdata/falcon-instruct.gotmpl/user-assistant-user create mode 100644 template/testdata/gemma-instruct.gotmpl/system-user-assistant-user create mode 100644 template/testdata/gemma-instruct.gotmpl/user create mode 100644 template/testdata/gemma-instruct.gotmpl/user-assistant-user create mode 100644 template/testdata/granite-instruct.gotmpl/system-user-assistant-user create mode 100644 template/testdata/granite-instruct.gotmpl/user create mode 100644 template/testdata/granite-instruct.gotmpl/user-assistant-user create mode 100644 template/testdata/llama2-chat.gotmpl/system-user-assistant-user create mode 100644 template/testdata/llama2-chat.gotmpl/user create mode 100644 template/testdata/llama2-chat.gotmpl/user-assistant-user create mode 100644 template/testdata/llama3-instruct.gotmpl/system-user-assistant-user create mode 100644 template/testdata/llama3-instruct.gotmpl/user create mode 100644 template/testdata/llama3-instruct.gotmpl/user-assistant-user create mode 100644 template/testdata/magicoder.gotmpl/system-user-assistant-user create mode 100644 template/testdata/magicoder.gotmpl/user create mode 100644 template/testdata/magicoder.gotmpl/user-assistant-user create mode 100644 template/testdata/mistral-instruct.gotmpl/system-user-assistant-user create mode 100644 template/testdata/mistral-instruct.gotmpl/user create mode 100644 template/testdata/mistral-instruct.gotmpl/user-assistant-user create mode 100644 template/testdata/openchat.gotmpl/system-user-assistant-user create mode 100644 template/testdata/openchat.gotmpl/user create mode 100644 template/testdata/openchat.gotmpl/user-assistant-user create mode 100644 template/testdata/phi-3.gotmpl/system-user-assistant-user create mode 100644 template/testdata/phi-3.gotmpl/user create mode 100644 template/testdata/phi-3.gotmpl/user-assistant-user create mode 100644 template/testdata/solar-instruct.gotmpl/system-user-assistant-user create mode 100644 template/testdata/solar-instruct.gotmpl/user create mode 100644 template/testdata/solar-instruct.gotmpl/user-assistant-user create mode 100644 template/testdata/starcoder2-instruct.gotmpl/system-user-assistant-user create mode 100644 template/testdata/starcoder2-instruct.gotmpl/user create mode 100644 template/testdata/starcoder2-instruct.gotmpl/user-assistant-user create mode 100644 template/testdata/vicuna.gotmpl/system-user-assistant-user create mode 100644 template/testdata/vicuna.gotmpl/user create mode 100644 template/testdata/vicuna.gotmpl/user-assistant-user create mode 100644 template/testdata/zephyr.gotmpl/system-user-assistant-user create mode 100644 template/testdata/zephyr.gotmpl/user create mode 100644 template/testdata/zephyr.gotmpl/user-assistant-user diff --git a/go.mod b/go.mod index 6807b9b48..2e0c6614c 100644 --- a/go.mod +++ b/go.mod @@ -18,6 +18,7 @@ require ( require ( github.com/agnivade/levenshtein v1.1.1 github.com/d4l3k/go-bfloat16 v0.0.0-20211005043715-690c3bdd05f1 + github.com/google/go-cmp v0.6.0 github.com/mattn/go-runewidth v0.0.14 github.com/nlpodyssey/gopickle v0.3.0 github.com/pdevine/tensor v0.0.0-20240510204454-f88f4562727c @@ -71,7 +72,7 @@ require ( golang.org/x/net v0.25.0 // indirect golang.org/x/sys v0.20.0 golang.org/x/term v0.20.0 - golang.org/x/text v0.15.0 // indirect + golang.org/x/text v0.15.0 google.golang.org/protobuf v1.34.1 gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/server/routes_create_test.go b/server/routes_create_test.go index 340612822..269a0ba12 100644 --- a/server/routes_create_test.go +++ b/server/routes_create_test.go @@ -545,9 +545,9 @@ func TestCreateDetectTemplate(t *testing.T) { } checkFileExists(t, filepath.Join(p, "blobs", "*"), []string{ - filepath.Join(p, "blobs", "sha256-2f8e594e6f34b1b4d36a246628eeb3365ce442303d656f1fcc69e821722acea0"), - filepath.Join(p, "blobs", "sha256-542b217f179c7825eeb5bca3c77d2b75ed05bafbd3451d9188891a60a85337c6"), filepath.Join(p, "blobs", "sha256-553c4a3f747b3d22a4946875f1cc8ed011c2930d83f864a0c7265f9ec0a20413"), + filepath.Join(p, "blobs", "sha256-9512c372dfc7d84d6065b8dd2b601aeed8cc1a78e7a7aa784a42fff37f5524b7"), + filepath.Join(p, "blobs", "sha256-b8b78cb8c6eefd14c06f1af042e6161255bf87bbf2dd14fce57cdac893db8139"), }) }) diff --git a/template/alfred.gotmpl b/template/alfred.gotmpl index cecb9d2c8..44284f04c 100644 --- a/template/alfred.gotmpl +++ b/template/alfred.gotmpl @@ -1 +1,8 @@ -{{ if .System }}{{ .System }}{{ end }}{{ if .Prompt }}{{ .Prompt }}{{ end }}{{ .Response }} \ No newline at end of file +{{- if .Messages }} +{{- if .System }}{{ .System }} +{{- end }} +{{- range .Messages }}{{ .Content }} +{{- end }} +{{- else }} +{{ if .System }}{{ .System }}{{ end }}{{ if .Prompt }}{{ .Prompt }}{{ end }}{{ .Response }} +{{- end }} \ No newline at end of file diff --git a/template/alpaca.gotmpl b/template/alpaca.gotmpl index 440d06627..c1f69dc92 100644 --- a/template/alpaca.gotmpl +++ b/template/alpaca.gotmpl @@ -1,7 +1,19 @@ +{{- if .Messages }} +{{- if .System }}{{ .System }} +{{- end }} +{{- range .Messages }} +{{- if eq .Role "user" }}### Instruction: +{{- else if eq .Role "assistant" }}### Response: +{{- end }} +{{ .Content }} + +{{ end }}### Response: +{{ else }} {{ if .System }}{{ .System }} {{ end }}{{ if .Prompt }}### Instruction: {{ .Prompt }} {{ end }}### Response: -{{ .Response }} \ No newline at end of file +{{ .Response }} +{{- end }} \ No newline at end of file diff --git a/template/chatml.gotmpl b/template/chatml.gotmpl index dcf172853..d945547c7 100644 --- a/template/chatml.gotmpl +++ b/template/chatml.gotmpl @@ -1,6 +1,15 @@ +{{- if .Messages }} +{{- if .System }}<|im_start|>system +{{ .System }}<|im_end|> +{{ end }} +{{- range .Messages }}<|im_start|>{{ .Role }} +{{ .Content }}<|im_end|> +{{ end }}<|im_start|>assistant +{{ else }} {{ if .System }}<|im_start|>system {{ .System }}<|im_end|> {{ end }}{{ if .Prompt }}<|im_start|>user {{ .Prompt }}<|im_end|> {{ end }}<|im_start|>assistant -{{ .Response }}<|im_end|> \ No newline at end of file +{{ .Response }}<|im_end|> +{{- end }} \ No newline at end of file diff --git a/template/chatqa.gotmpl b/template/chatqa.gotmpl index 1ede6227f..7022c4790 100644 --- a/template/chatqa.gotmpl +++ b/template/chatqa.gotmpl @@ -1,5 +1,17 @@ +{{- if .Messages }} +{{- if .System }}System: {{ .System }} + +{{ end }} +{{- range .Messages }} +{{- if eq .Role "user" }}User: +{{- else if eq .Role "assistant" }}Assistant: +{{- end }} {{ .Content }} + +{{ end }}Assistant: +{{- else }} {{ if .System }}System: {{ .System }} {{ end }}{{ if .Prompt }}User: {{ .Prompt }} -{{ end }}Assistant: <|begin_of_text|>{{ .Response }} \ No newline at end of file +{{ end }}Assistant: <|begin_of_text|>{{ .Response }} +{{- end }} \ No newline at end of file diff --git a/template/codellama-70b-instruct.gotmpl b/template/codellama-70b-instruct.gotmpl index 3196bd6fd..392d839eb 100644 --- a/template/codellama-70b-instruct.gotmpl +++ b/template/codellama-70b-instruct.gotmpl @@ -1,3 +1,13 @@ +{{- if .Messages }} +{{- if .System }}Source: system + + {{ .System }} {{ end }} +{{- range .Messages }}Source: {{ .Role }} + + {{ .Content }} {{ end }}Source: assistant +Destination: user + +{{ else }} {{ if .System }} Source: system {{ .System }} {{ end }} Source: user @@ -5,4 +15,5 @@ {{ .Prompt }} Source: assistant Destination: user - {{ .Response }} \ No newline at end of file + {{ .Response }} +{{- end }} \ No newline at end of file diff --git a/template/falcon-instruct.gotmpl b/template/falcon-instruct.gotmpl index 2309a1c5d..99d67f93c 100644 --- a/template/falcon-instruct.gotmpl +++ b/template/falcon-instruct.gotmpl @@ -1,3 +1,13 @@ +{{- if .Messages }} +{{- if .System }}System: {{ .System }} +{{ end }} +{{- range .Messages }} +{{- if eq .Role "user" }}User: +{{ else if eq .Role "assistant" }}Falcon: +{{ end }}{{ .Content }} +{{ end }}Falcon: +{{ else }} {{ if .System }}{{ .System }} {{ end }}{{ if .Prompt }}User: {{ .Prompt }} -{{ end }}Assistant: {{ .Response }} \ No newline at end of file +{{ end }}Assistant: {{ .Response }} +{{- end }} \ No newline at end of file diff --git a/template/gemma-instruct.gotmpl b/template/gemma-instruct.gotmpl index 91b9883a1..870a8f2e2 100644 --- a/template/gemma-instruct.gotmpl +++ b/template/gemma-instruct.gotmpl @@ -1,4 +1,16 @@ +{{- if .Messages }} +{{- range $index, $_ := .Messages }} +{{- if eq .Role "user" }}user +{{- if and $.System (eq $index 0) }} +{{ $.System }} +{{- end }} +{{- else if eq .Role "assistant" }}model +{{- end }} +{{ .Content }} +{{ end }}model +{{ else }} user {{ if .System }}{{ .System }} {{ end }}{{ .Prompt }} model -{{ .Response }} \ No newline at end of file +{{ .Response }} +{{- end }} \ No newline at end of file diff --git a/template/granite-instruct.gotmpl b/template/granite-instruct.gotmpl index 2ede647f5..327ff3eef 100644 --- a/template/granite-instruct.gotmpl +++ b/template/granite-instruct.gotmpl @@ -1,3 +1,16 @@ +{{- if .Messages }} +{{- if .System }}System: +{{ .System }} + +{{ end }} +{{- range .Messages }} +{{- if eq .Role "user" }}Question: +{{- else if eq .Role "assistant" }}Answer: +{{- end }} +{{ .Content }} + +{{ end }}Answer: +{{ else }} {{ if .System }} System: {{ .System }} @@ -6,4 +19,5 @@ System: {{ .Prompt }} {{ end }}Answer: -{{ .Response }} \ No newline at end of file +{{ .Response }} +{{- end }} \ No newline at end of file diff --git a/template/llama2-chat.gotmpl b/template/llama2-chat.gotmpl index a739f6908..6327d5812 100644 --- a/template/llama2-chat.gotmpl +++ b/template/llama2-chat.gotmpl @@ -1,3 +1,16 @@ +{{- if .Messages }} +{{- range $index, $_ := .Messages }} +{{- if eq .Role "user" }}[INST] {{ if eq $index 0 }}<> +{{- if $.System }} +{{ $.System }} +{{ end }}<> + +{{ end }}{{ .Content }} +{{- else }} [/INST] {{ .Content }} +{{- end }} +{{- end }} [/INST] +{{- else }} [INST] <>{{ .System }}<> -{{ .Prompt }} [/INST] {{ .Response }} \ No newline at end of file +{{ .Prompt }} [/INST] {{ .Response }} +{{- end }} \ No newline at end of file diff --git a/template/llama3-instruct.gotmpl b/template/llama3-instruct.gotmpl index 36d0218b6..9c81a9535 100644 --- a/template/llama3-instruct.gotmpl +++ b/template/llama3-instruct.gotmpl @@ -1,7 +1,19 @@ +{{- if .Messages }} +{{- if .System }}<|start_header_id|>system<|end_header_id|> + +{{ .System }}<|eot_id|> +{{- end }} +{{- range .Messages }}<|start_header_id|>{{ .Role }}<|end_header_id|> + +{{ .Content }}<|eot_id|> +{{- end }}<|start_header_id|>assistant<|end_header_id|> + +{{ else }} {{ if .System }}<|start_header_id|>system<|end_header_id|> {{ .System }}<|eot_id|>{{ end }}{{ if .Prompt }}<|start_header_id|>user<|end_header_id|> {{ .Prompt }}<|eot_id|>{{ end }}<|start_header_id|>assistant<|end_header_id|> -{{ .Response }}<|eot_id|> \ No newline at end of file +{{ .Response }}<|eot_id|> +{{- end }} \ No newline at end of file diff --git a/template/magicoder.gotmpl b/template/magicoder.gotmpl index 306972ecc..73a58127c 100644 --- a/template/magicoder.gotmpl +++ b/template/magicoder.gotmpl @@ -1,7 +1,20 @@ +{{- if .Messages }} +{{- if .System }}{{ .System }} + +{{ end }} +{{- range .Messages }} +{{- if eq .Role "user" }}@@ Instruction +{{- else if eq .Role "assistant" }}@@ Response +{{- end }} +{{ .Content }} + +{{ end }}@@ Response +{{ else }} {{ if .System }}{{ .System }} {{ end }}{{ if .Prompt }}@@ Instruction {{ .Prompt }} {{ end }}@@ Response -{{ .Response }} \ No newline at end of file +{{ .Response }} +{{- end }} \ No newline at end of file diff --git a/template/mistral-instruct.gotmpl b/template/mistral-instruct.gotmpl index dcf172853..eb3d5ced2 100644 --- a/template/mistral-instruct.gotmpl +++ b/template/mistral-instruct.gotmpl @@ -1,6 +1,9 @@ -{{ if .System }}<|im_start|>system -{{ .System }}<|im_end|> -{{ end }}{{ if .Prompt }}<|im_start|>user -{{ .Prompt }}<|im_end|> -{{ end }}<|im_start|>assistant -{{ .Response }}<|im_end|> \ No newline at end of file +{{- if .Messages }} +{{- range $index, $_ := .Messages }} +{{- if eq .Role "user" }}[INST] {{ if and $.System (eq (len (slice $.Messages $index)) 1) }}{{ $.System }} +{{ end }}{{ .Content }} +{{- else if eq .Role "assistant" }}[/INST] {{ .Content }} +{{- end }} +{{- end }}[/INST] +{{- else }}[INST] {{ if .System }}{{ .System }} {{ end }}{{ .Prompt }} [/INST] {{ .Response }} +{{- end }} \ No newline at end of file diff --git a/template/openchat.gotmpl b/template/openchat.gotmpl index d2ca38685..d5e1cbb0d 100644 --- a/template/openchat.gotmpl +++ b/template/openchat.gotmpl @@ -1 +1,11 @@ -{{ .System }}<|end_of_turn|>GPT4 Correct User: {{ .Prompt }}<|end_of_turn|>GPT4 Correct Assistant: {{ .Response }}<|end_of_turn|> \ No newline at end of file +{{- if .Messages }} +{{- if .System }}GPT Correct System: {{ .System }}<|end_of_turn|> +{{- end }} +{{- range .Messages }}GPT Correct +{{- if eq .Role "user" }} User: +{{- else if eq .Role "assistant" }} Assistant: +{{- end }} {{ .Content }}<|end_of_turn|> +{{- end }}GPT Correct Assistant: +{{- else }} +{{ .System }}<|end_of_turn|>GPT4 Correct User: {{ .Prompt }}<|end_of_turn|>GPT4 Correct Assistant: {{ .Response }}<|end_of_turn|> +{{- end }} \ No newline at end of file diff --git a/template/phi-3.gotmpl b/template/phi-3.gotmpl index bf26dcee2..a3558d2b7 100644 --- a/template/phi-3.gotmpl +++ b/template/phi-3.gotmpl @@ -1,6 +1,15 @@ +{{- if .Messages }} +{{- if .System }}<|system|> +{{ .System }}<|end|> +{{ end }} +{{- range .Messages }}<|{{ .Role }}|> +{{ .Content }}<|end|> +{{ end }}<|assistant|> +{{ else }} {{ if .System }}<|system|> {{ .System }}<|end|> {{ end }}{{ if .Prompt }}<|user|> {{ .Prompt }}<|end|> {{ end }}<|assistant|> -{{ .Response }}<|end|> \ No newline at end of file +{{ .Response }}<|end|> +{{- end }} \ No newline at end of file diff --git a/template/solar-instruct.gotmpl b/template/solar-instruct.gotmpl index c275a26a3..caa6e8e77 100644 --- a/template/solar-instruct.gotmpl +++ b/template/solar-instruct.gotmpl @@ -1,3 +1,16 @@ +{{- if .Messages }} +{{- if .System }}### System: +{{ .System }} + +{{ end }} +{{- range .Messages }} +{{- if eq .Role "user" }}### User: +{{ .Content }} +{{ else if eq .Role "assistant" }}### Assistant: +{{ .Content }} +{{ end }} +{{ end }}### Assistant: +{{ else }} {{ if .System }}### System: {{ .System }} @@ -5,4 +18,5 @@ {{ .Prompt }} {{ end }}### Assistant: -{{ .Response }} \ No newline at end of file +{{ .Response }} +{{- end }} \ No newline at end of file diff --git a/template/starcoder2-instruct.gotmpl b/template/starcoder2-instruct.gotmpl index 33357e54e..7d7ff9326 100644 --- a/template/starcoder2-instruct.gotmpl +++ b/template/starcoder2-instruct.gotmpl @@ -1,3 +1,17 @@ +{{- if .Messages }} +{{- if .System }}{{ .System }} + +{{ end }} +{{- range .Messages }} +{{- if eq .Role "user" }}### Instruction +{{ .Content }} + +{{ else if eq .Role "assistant" }}### Response +{{ .Content }}<|endoftext|> + +{{ end }} +{{- end }}### Response +{{ else }} {{ if .System }}{{ .System }} {{ end }}{{ if .Prompt }}### Instruction @@ -7,3 +21,4 @@ {{ end }}### Response {{ .Response }}<|endoftext|> +{{- end }} \ No newline at end of file diff --git a/template/template_test.go b/template/template_test.go index ac16bd606..428cdc77c 100644 --- a/template/template_test.go +++ b/template/template_test.go @@ -8,9 +8,10 @@ import ( "os" "path/filepath" "slices" + "strings" "testing" - "text/template" + "github.com/google/go-cmp/cmp" "github.com/ollama/ollama/api" "github.com/ollama/ollama/llm" ) @@ -47,7 +48,7 @@ func TestNamed(t *testing.T) { t.Fatal(err) } - tmpl, err := template.New(s).Parse(b.String()) + tmpl, err := Parse(b.String()) if err != nil { t.Fatal(err) } @@ -60,6 +61,70 @@ func TestNamed(t *testing.T) { } } +func TestTemplate(t *testing.T) { + cases := make(map[string][]api.Message) + for _, mm := range [][]api.Message{ + { + {Role: "user", Content: "Hello, how are you?"}, + }, + { + {Role: "user", Content: "Hello, how are you?"}, + {Role: "assistant", Content: "I'm doing great. How can I help you today?"}, + {Role: "user", Content: "I'd like to show off how chat templating works!"}, + }, + { + {Role: "system", Content: "You are a helpful assistant."}, + {Role: "user", Content: "Hello, how are you?"}, + {Role: "assistant", Content: "I'm doing great. How can I help you today?"}, + {Role: "user", Content: "I'd like to show off how chat templating works!"}, + }, + } { + var roles []string + for _, m := range mm { + roles = append(roles, m.Role) + } + + cases[strings.Join(roles, "-")] = mm + } + + matches, err := filepath.Glob("*.gotmpl") + if err != nil { + t.Fatal(err) + } + + for _, match := range matches { + t.Run(match, func(t *testing.T) { + bts, err := os.ReadFile(match) + if err != nil { + t.Fatal(err) + } + + tmpl, err := Parse(string(bts)) + if err != nil { + t.Fatal(err) + } + + for n, tt := range cases { + t.Run(n, func(t *testing.T) { + var actual bytes.Buffer + if err := tmpl.Execute(&actual, Values{Messages: tt}); err != nil { + t.Fatal(err) + } + + expect, err := os.ReadFile(filepath.Join("testdata", match, n)) + if err != nil { + t.Fatal(err) + } + + if diff := cmp.Diff(actual.Bytes(), expect); diff != "" { + t.Errorf("mismatch (-got +want):\n%s", diff) + } + }) + } + }) + } +} + func TestParse(t *testing.T) { cases := []struct { template string diff --git a/template/testdata/alfred.gotmpl/system-user-assistant-user b/template/testdata/alfred.gotmpl/system-user-assistant-user new file mode 100644 index 000000000..03e23ea9c --- /dev/null +++ b/template/testdata/alfred.gotmpl/system-user-assistant-user @@ -0,0 +1 @@ +You are a helpful assistant.Hello, how are you?I'm doing great. How can I help you today?I'd like to show off how chat templating works! \ No newline at end of file diff --git a/template/testdata/alfred.gotmpl/user b/template/testdata/alfred.gotmpl/user new file mode 100644 index 000000000..7c884a6f0 --- /dev/null +++ b/template/testdata/alfred.gotmpl/user @@ -0,0 +1 @@ +Hello, how are you? \ No newline at end of file diff --git a/template/testdata/alfred.gotmpl/user-assistant-user b/template/testdata/alfred.gotmpl/user-assistant-user new file mode 100644 index 000000000..a60701ed7 --- /dev/null +++ b/template/testdata/alfred.gotmpl/user-assistant-user @@ -0,0 +1 @@ +Hello, how are you?I'm doing great. How can I help you today?I'd like to show off how chat templating works! \ No newline at end of file diff --git a/template/testdata/alpaca.gotmpl/system-user-assistant-user b/template/testdata/alpaca.gotmpl/system-user-assistant-user new file mode 100644 index 000000000..20182d829 --- /dev/null +++ b/template/testdata/alpaca.gotmpl/system-user-assistant-user @@ -0,0 +1,10 @@ +You are a helpful assistant.### Instruction: +Hello, how are you? + +### Response: +I'm doing great. How can I help you today? + +### Instruction: +I'd like to show off how chat templating works! + +### Response: diff --git a/template/testdata/alpaca.gotmpl/user b/template/testdata/alpaca.gotmpl/user new file mode 100644 index 000000000..a0ce5dec7 --- /dev/null +++ b/template/testdata/alpaca.gotmpl/user @@ -0,0 +1,4 @@ +### Instruction: +Hello, how are you? + +### Response: diff --git a/template/testdata/alpaca.gotmpl/user-assistant-user b/template/testdata/alpaca.gotmpl/user-assistant-user new file mode 100644 index 000000000..6c5e23ff5 --- /dev/null +++ b/template/testdata/alpaca.gotmpl/user-assistant-user @@ -0,0 +1,10 @@ +### Instruction: +Hello, how are you? + +### Response: +I'm doing great. How can I help you today? + +### Instruction: +I'd like to show off how chat templating works! + +### Response: diff --git a/template/testdata/chatml.gotmpl/system-user-assistant-user b/template/testdata/chatml.gotmpl/system-user-assistant-user new file mode 100644 index 000000000..8b013fcfb --- /dev/null +++ b/template/testdata/chatml.gotmpl/system-user-assistant-user @@ -0,0 +1,9 @@ +<|im_start|>system +You are a helpful assistant.<|im_end|> +<|im_start|>user +Hello, how are you?<|im_end|> +<|im_start|>assistant +I'm doing great. How can I help you today?<|im_end|> +<|im_start|>user +I'd like to show off how chat templating works!<|im_end|> +<|im_start|>assistant diff --git a/template/testdata/chatml.gotmpl/user b/template/testdata/chatml.gotmpl/user new file mode 100644 index 000000000..aa9e597a4 --- /dev/null +++ b/template/testdata/chatml.gotmpl/user @@ -0,0 +1,3 @@ +<|im_start|>user +Hello, how are you?<|im_end|> +<|im_start|>assistant diff --git a/template/testdata/chatml.gotmpl/user-assistant-user b/template/testdata/chatml.gotmpl/user-assistant-user new file mode 100644 index 000000000..a7cba4de0 --- /dev/null +++ b/template/testdata/chatml.gotmpl/user-assistant-user @@ -0,0 +1,7 @@ +<|im_start|>user +Hello, how are you?<|im_end|> +<|im_start|>assistant +I'm doing great. How can I help you today?<|im_end|> +<|im_start|>user +I'd like to show off how chat templating works!<|im_end|> +<|im_start|>assistant diff --git a/template/testdata/chatqa.gotmpl/system-user-assistant-user b/template/testdata/chatqa.gotmpl/system-user-assistant-user new file mode 100644 index 000000000..98fd59bfa --- /dev/null +++ b/template/testdata/chatqa.gotmpl/system-user-assistant-user @@ -0,0 +1,9 @@ +System: You are a helpful assistant. + +User: Hello, how are you? + +Assistant: I'm doing great. How can I help you today? + +User: I'd like to show off how chat templating works! + +Assistant: \ No newline at end of file diff --git a/template/testdata/chatqa.gotmpl/user b/template/testdata/chatqa.gotmpl/user new file mode 100644 index 000000000..9e7cf702d --- /dev/null +++ b/template/testdata/chatqa.gotmpl/user @@ -0,0 +1,3 @@ +User: Hello, how are you? + +Assistant: \ No newline at end of file diff --git a/template/testdata/chatqa.gotmpl/user-assistant-user b/template/testdata/chatqa.gotmpl/user-assistant-user new file mode 100644 index 000000000..405bbe12c --- /dev/null +++ b/template/testdata/chatqa.gotmpl/user-assistant-user @@ -0,0 +1,7 @@ +User: Hello, how are you? + +Assistant: I'm doing great. How can I help you today? + +User: I'd like to show off how chat templating works! + +Assistant: \ No newline at end of file diff --git a/template/testdata/codellama-70b-instruct.gotmpl/system-user-assistant-user b/template/testdata/codellama-70b-instruct.gotmpl/system-user-assistant-user new file mode 100644 index 000000000..fdd0fc8b4 --- /dev/null +++ b/template/testdata/codellama-70b-instruct.gotmpl/system-user-assistant-user @@ -0,0 +1,11 @@ +Source: system + + You are a helpful assistant. Source: user + + Hello, how are you? Source: assistant + + I'm doing great. How can I help you today? Source: user + + I'd like to show off how chat templating works! Source: assistant +Destination: user + diff --git a/template/testdata/codellama-70b-instruct.gotmpl/user b/template/testdata/codellama-70b-instruct.gotmpl/user new file mode 100644 index 000000000..9e7174a84 --- /dev/null +++ b/template/testdata/codellama-70b-instruct.gotmpl/user @@ -0,0 +1,5 @@ +Source: user + + Hello, how are you? Source: assistant +Destination: user + diff --git a/template/testdata/codellama-70b-instruct.gotmpl/user-assistant-user b/template/testdata/codellama-70b-instruct.gotmpl/user-assistant-user new file mode 100644 index 000000000..b4ba1736b --- /dev/null +++ b/template/testdata/codellama-70b-instruct.gotmpl/user-assistant-user @@ -0,0 +1,9 @@ +Source: user + + Hello, how are you? Source: assistant + + I'm doing great. How can I help you today? Source: user + + I'd like to show off how chat templating works! Source: assistant +Destination: user + diff --git a/template/testdata/falcon-instruct.gotmpl/system-user-assistant-user b/template/testdata/falcon-instruct.gotmpl/system-user-assistant-user new file mode 100644 index 000000000..16e45e5b6 --- /dev/null +++ b/template/testdata/falcon-instruct.gotmpl/system-user-assistant-user @@ -0,0 +1,8 @@ +System: You are a helpful assistant. +User: +Hello, how are you? +Falcon: +I'm doing great. How can I help you today? +User: +I'd like to show off how chat templating works! +Falcon: diff --git a/template/testdata/falcon-instruct.gotmpl/user b/template/testdata/falcon-instruct.gotmpl/user new file mode 100644 index 000000000..110831a2c --- /dev/null +++ b/template/testdata/falcon-instruct.gotmpl/user @@ -0,0 +1,3 @@ +User: +Hello, how are you? +Falcon: diff --git a/template/testdata/falcon-instruct.gotmpl/user-assistant-user b/template/testdata/falcon-instruct.gotmpl/user-assistant-user new file mode 100644 index 000000000..b49639ea5 --- /dev/null +++ b/template/testdata/falcon-instruct.gotmpl/user-assistant-user @@ -0,0 +1,7 @@ +User: +Hello, how are you? +Falcon: +I'm doing great. How can I help you today? +User: +I'd like to show off how chat templating works! +Falcon: diff --git a/template/testdata/gemma-instruct.gotmpl/system-user-assistant-user b/template/testdata/gemma-instruct.gotmpl/system-user-assistant-user new file mode 100644 index 000000000..5f6c37324 --- /dev/null +++ b/template/testdata/gemma-instruct.gotmpl/system-user-assistant-user @@ -0,0 +1,8 @@ +user +You are a helpful assistant. +Hello, how are you? +model +I'm doing great. How can I help you today? +user +I'd like to show off how chat templating works! +model diff --git a/template/testdata/gemma-instruct.gotmpl/user b/template/testdata/gemma-instruct.gotmpl/user new file mode 100644 index 000000000..dc8b30b68 --- /dev/null +++ b/template/testdata/gemma-instruct.gotmpl/user @@ -0,0 +1,3 @@ +user +Hello, how are you? +model diff --git a/template/testdata/gemma-instruct.gotmpl/user-assistant-user b/template/testdata/gemma-instruct.gotmpl/user-assistant-user new file mode 100644 index 000000000..1185924b0 --- /dev/null +++ b/template/testdata/gemma-instruct.gotmpl/user-assistant-user @@ -0,0 +1,7 @@ +user +Hello, how are you? +model +I'm doing great. How can I help you today? +user +I'd like to show off how chat templating works! +model diff --git a/template/testdata/granite-instruct.gotmpl/system-user-assistant-user b/template/testdata/granite-instruct.gotmpl/system-user-assistant-user new file mode 100644 index 000000000..a732a77fb --- /dev/null +++ b/template/testdata/granite-instruct.gotmpl/system-user-assistant-user @@ -0,0 +1,13 @@ +System: +You are a helpful assistant. + +Question: +Hello, how are you? + +Answer: +I'm doing great. How can I help you today? + +Question: +I'd like to show off how chat templating works! + +Answer: diff --git a/template/testdata/granite-instruct.gotmpl/user b/template/testdata/granite-instruct.gotmpl/user new file mode 100644 index 000000000..7abd2ea35 --- /dev/null +++ b/template/testdata/granite-instruct.gotmpl/user @@ -0,0 +1,4 @@ +Question: +Hello, how are you? + +Answer: diff --git a/template/testdata/granite-instruct.gotmpl/user-assistant-user b/template/testdata/granite-instruct.gotmpl/user-assistant-user new file mode 100644 index 000000000..da5e43eae --- /dev/null +++ b/template/testdata/granite-instruct.gotmpl/user-assistant-user @@ -0,0 +1,10 @@ +Question: +Hello, how are you? + +Answer: +I'm doing great. How can I help you today? + +Question: +I'd like to show off how chat templating works! + +Answer: diff --git a/template/testdata/llama2-chat.gotmpl/system-user-assistant-user b/template/testdata/llama2-chat.gotmpl/system-user-assistant-user new file mode 100644 index 000000000..fc2679bf0 --- /dev/null +++ b/template/testdata/llama2-chat.gotmpl/system-user-assistant-user @@ -0,0 +1,5 @@ +[INST] <> +You are a helpful assistant. +<> + +Hello, how are you? [/INST] I'm doing great. How can I help you today?[INST] I'd like to show off how chat templating works! [/INST] \ No newline at end of file diff --git a/template/testdata/llama2-chat.gotmpl/user b/template/testdata/llama2-chat.gotmpl/user new file mode 100644 index 000000000..ceef9bdbb --- /dev/null +++ b/template/testdata/llama2-chat.gotmpl/user @@ -0,0 +1,3 @@ +[INST] <><> + +Hello, how are you? [/INST] \ No newline at end of file diff --git a/template/testdata/llama2-chat.gotmpl/user-assistant-user b/template/testdata/llama2-chat.gotmpl/user-assistant-user new file mode 100644 index 000000000..42b4c5294 --- /dev/null +++ b/template/testdata/llama2-chat.gotmpl/user-assistant-user @@ -0,0 +1,3 @@ +[INST] <><> + +Hello, how are you? [/INST] I'm doing great. How can I help you today?[INST] I'd like to show off how chat templating works! [/INST] \ No newline at end of file diff --git a/template/testdata/llama3-instruct.gotmpl/system-user-assistant-user b/template/testdata/llama3-instruct.gotmpl/system-user-assistant-user new file mode 100644 index 000000000..6740bcb4d --- /dev/null +++ b/template/testdata/llama3-instruct.gotmpl/system-user-assistant-user @@ -0,0 +1,10 @@ +<|start_header_id|>system<|end_header_id|> + +You are a helpful assistant.<|eot_id|><|start_header_id|>user<|end_header_id|> + +Hello, how are you?<|eot_id|><|start_header_id|>assistant<|end_header_id|> + +I'm doing great. How can I help you today?<|eot_id|><|start_header_id|>user<|end_header_id|> + +I'd like to show off how chat templating works!<|eot_id|><|start_header_id|>assistant<|end_header_id|> + diff --git a/template/testdata/llama3-instruct.gotmpl/user b/template/testdata/llama3-instruct.gotmpl/user new file mode 100644 index 000000000..470aa028f --- /dev/null +++ b/template/testdata/llama3-instruct.gotmpl/user @@ -0,0 +1,4 @@ +<|start_header_id|>user<|end_header_id|> + +Hello, how are you?<|eot_id|><|start_header_id|>assistant<|end_header_id|> + diff --git a/template/testdata/llama3-instruct.gotmpl/user-assistant-user b/template/testdata/llama3-instruct.gotmpl/user-assistant-user new file mode 100644 index 000000000..6dd768af5 --- /dev/null +++ b/template/testdata/llama3-instruct.gotmpl/user-assistant-user @@ -0,0 +1,8 @@ +<|start_header_id|>user<|end_header_id|> + +Hello, how are you?<|eot_id|><|start_header_id|>assistant<|end_header_id|> + +I'm doing great. How can I help you today?<|eot_id|><|start_header_id|>user<|end_header_id|> + +I'd like to show off how chat templating works!<|eot_id|><|start_header_id|>assistant<|end_header_id|> + diff --git a/template/testdata/magicoder.gotmpl/system-user-assistant-user b/template/testdata/magicoder.gotmpl/system-user-assistant-user new file mode 100644 index 000000000..c966a861d --- /dev/null +++ b/template/testdata/magicoder.gotmpl/system-user-assistant-user @@ -0,0 +1,12 @@ +You are a helpful assistant. + +@@ Instruction +Hello, how are you? + +@@ Response +I'm doing great. How can I help you today? + +@@ Instruction +I'd like to show off how chat templating works! + +@@ Response diff --git a/template/testdata/magicoder.gotmpl/user b/template/testdata/magicoder.gotmpl/user new file mode 100644 index 000000000..ccfb02bd2 --- /dev/null +++ b/template/testdata/magicoder.gotmpl/user @@ -0,0 +1,4 @@ +@@ Instruction +Hello, how are you? + +@@ Response diff --git a/template/testdata/magicoder.gotmpl/user-assistant-user b/template/testdata/magicoder.gotmpl/user-assistant-user new file mode 100644 index 000000000..3aea6dab9 --- /dev/null +++ b/template/testdata/magicoder.gotmpl/user-assistant-user @@ -0,0 +1,10 @@ +@@ Instruction +Hello, how are you? + +@@ Response +I'm doing great. How can I help you today? + +@@ Instruction +I'd like to show off how chat templating works! + +@@ Response diff --git a/template/testdata/mistral-instruct.gotmpl/system-user-assistant-user b/template/testdata/mistral-instruct.gotmpl/system-user-assistant-user new file mode 100644 index 000000000..b6b4bf93e --- /dev/null +++ b/template/testdata/mistral-instruct.gotmpl/system-user-assistant-user @@ -0,0 +1,2 @@ +[INST] Hello, how are you?[/INST] I'm doing great. How can I help you today?[INST] You are a helpful assistant. +I'd like to show off how chat templating works![/INST] \ No newline at end of file diff --git a/template/testdata/mistral-instruct.gotmpl/user b/template/testdata/mistral-instruct.gotmpl/user new file mode 100644 index 000000000..b04871e5d --- /dev/null +++ b/template/testdata/mistral-instruct.gotmpl/user @@ -0,0 +1 @@ +[INST] Hello, how are you?[/INST] \ No newline at end of file diff --git a/template/testdata/mistral-instruct.gotmpl/user-assistant-user b/template/testdata/mistral-instruct.gotmpl/user-assistant-user new file mode 100644 index 000000000..b473e0df0 --- /dev/null +++ b/template/testdata/mistral-instruct.gotmpl/user-assistant-user @@ -0,0 +1 @@ +[INST] Hello, how are you?[/INST] I'm doing great. How can I help you today?[INST] I'd like to show off how chat templating works![/INST] \ No newline at end of file diff --git a/template/testdata/openchat.gotmpl/system-user-assistant-user b/template/testdata/openchat.gotmpl/system-user-assistant-user new file mode 100644 index 000000000..1214c1264 --- /dev/null +++ b/template/testdata/openchat.gotmpl/system-user-assistant-user @@ -0,0 +1 @@ +GPT Correct System: You are a helpful assistant.<|end_of_turn|>GPT Correct User: Hello, how are you?<|end_of_turn|>GPT Correct Assistant: I'm doing great. How can I help you today?<|end_of_turn|>GPT Correct User: I'd like to show off how chat templating works!<|end_of_turn|>GPT Correct Assistant: \ No newline at end of file diff --git a/template/testdata/openchat.gotmpl/user b/template/testdata/openchat.gotmpl/user new file mode 100644 index 000000000..611daa83e --- /dev/null +++ b/template/testdata/openchat.gotmpl/user @@ -0,0 +1 @@ +GPT Correct User: Hello, how are you?<|end_of_turn|>GPT Correct Assistant: \ No newline at end of file diff --git a/template/testdata/openchat.gotmpl/user-assistant-user b/template/testdata/openchat.gotmpl/user-assistant-user new file mode 100644 index 000000000..f97b02b9c --- /dev/null +++ b/template/testdata/openchat.gotmpl/user-assistant-user @@ -0,0 +1 @@ +GPT Correct User: Hello, how are you?<|end_of_turn|>GPT Correct Assistant: I'm doing great. How can I help you today?<|end_of_turn|>GPT Correct User: I'd like to show off how chat templating works!<|end_of_turn|>GPT Correct Assistant: \ No newline at end of file diff --git a/template/testdata/phi-3.gotmpl/system-user-assistant-user b/template/testdata/phi-3.gotmpl/system-user-assistant-user new file mode 100644 index 000000000..6109a9a24 --- /dev/null +++ b/template/testdata/phi-3.gotmpl/system-user-assistant-user @@ -0,0 +1,9 @@ +<|system|> +You are a helpful assistant.<|end|> +<|user|> +Hello, how are you?<|end|> +<|assistant|> +I'm doing great. How can I help you today?<|end|> +<|user|> +I'd like to show off how chat templating works!<|end|> +<|assistant|> diff --git a/template/testdata/phi-3.gotmpl/user b/template/testdata/phi-3.gotmpl/user new file mode 100644 index 000000000..feb96e7c9 --- /dev/null +++ b/template/testdata/phi-3.gotmpl/user @@ -0,0 +1,3 @@ +<|user|> +Hello, how are you?<|end|> +<|assistant|> diff --git a/template/testdata/phi-3.gotmpl/user-assistant-user b/template/testdata/phi-3.gotmpl/user-assistant-user new file mode 100644 index 000000000..db79d01c1 --- /dev/null +++ b/template/testdata/phi-3.gotmpl/user-assistant-user @@ -0,0 +1,7 @@ +<|user|> +Hello, how are you?<|end|> +<|assistant|> +I'm doing great. How can I help you today?<|end|> +<|user|> +I'd like to show off how chat templating works!<|end|> +<|assistant|> diff --git a/template/testdata/solar-instruct.gotmpl/system-user-assistant-user b/template/testdata/solar-instruct.gotmpl/system-user-assistant-user new file mode 100644 index 000000000..28c1730ab --- /dev/null +++ b/template/testdata/solar-instruct.gotmpl/system-user-assistant-user @@ -0,0 +1,13 @@ +### System: +You are a helpful assistant. + +### User: +Hello, how are you? + +### Assistant: +I'm doing great. How can I help you today? + +### User: +I'd like to show off how chat templating works! + +### Assistant: diff --git a/template/testdata/solar-instruct.gotmpl/user b/template/testdata/solar-instruct.gotmpl/user new file mode 100644 index 000000000..3a43382af --- /dev/null +++ b/template/testdata/solar-instruct.gotmpl/user @@ -0,0 +1,4 @@ +### User: +Hello, how are you? + +### Assistant: diff --git a/template/testdata/solar-instruct.gotmpl/user-assistant-user b/template/testdata/solar-instruct.gotmpl/user-assistant-user new file mode 100644 index 000000000..8553e73ba --- /dev/null +++ b/template/testdata/solar-instruct.gotmpl/user-assistant-user @@ -0,0 +1,10 @@ +### User: +Hello, how are you? + +### Assistant: +I'm doing great. How can I help you today? + +### User: +I'd like to show off how chat templating works! + +### Assistant: diff --git a/template/testdata/starcoder2-instruct.gotmpl/system-user-assistant-user b/template/testdata/starcoder2-instruct.gotmpl/system-user-assistant-user new file mode 100644 index 000000000..5b718b3ec --- /dev/null +++ b/template/testdata/starcoder2-instruct.gotmpl/system-user-assistant-user @@ -0,0 +1,12 @@ +You are a helpful assistant. + +### Instruction +Hello, how are you? + +### Response +I'm doing great. How can I help you today?<|endoftext|> + +### Instruction +I'd like to show off how chat templating works! + +### Response diff --git a/template/testdata/starcoder2-instruct.gotmpl/user b/template/testdata/starcoder2-instruct.gotmpl/user new file mode 100644 index 000000000..11b0be1fe --- /dev/null +++ b/template/testdata/starcoder2-instruct.gotmpl/user @@ -0,0 +1,4 @@ +### Instruction +Hello, how are you? + +### Response diff --git a/template/testdata/starcoder2-instruct.gotmpl/user-assistant-user b/template/testdata/starcoder2-instruct.gotmpl/user-assistant-user new file mode 100644 index 000000000..d99feabb0 --- /dev/null +++ b/template/testdata/starcoder2-instruct.gotmpl/user-assistant-user @@ -0,0 +1,10 @@ +### Instruction +Hello, how are you? + +### Response +I'm doing great. How can I help you today?<|endoftext|> + +### Instruction +I'd like to show off how chat templating works! + +### Response diff --git a/template/testdata/vicuna.gotmpl/system-user-assistant-user b/template/testdata/vicuna.gotmpl/system-user-assistant-user new file mode 100644 index 000000000..50d2f92c1 --- /dev/null +++ b/template/testdata/vicuna.gotmpl/system-user-assistant-user @@ -0,0 +1,6 @@ +You are a helpful assistant. + +USER: Hello, how are you? +ASSISTANT: I'm doing great. How can I help you today? +USER: I'd like to show off how chat templating works! +ASSISTANT: \ No newline at end of file diff --git a/template/testdata/vicuna.gotmpl/user b/template/testdata/vicuna.gotmpl/user new file mode 100644 index 000000000..cbe5ef709 --- /dev/null +++ b/template/testdata/vicuna.gotmpl/user @@ -0,0 +1,2 @@ +USER: Hello, how are you? +ASSISTANT: \ No newline at end of file diff --git a/template/testdata/vicuna.gotmpl/user-assistant-user b/template/testdata/vicuna.gotmpl/user-assistant-user new file mode 100644 index 000000000..9172547e3 --- /dev/null +++ b/template/testdata/vicuna.gotmpl/user-assistant-user @@ -0,0 +1,4 @@ +USER: Hello, how are you? +ASSISTANT: I'm doing great. How can I help you today? +USER: I'd like to show off how chat templating works! +ASSISTANT: \ No newline at end of file diff --git a/template/testdata/zephyr.gotmpl/system-user-assistant-user b/template/testdata/zephyr.gotmpl/system-user-assistant-user new file mode 100644 index 000000000..03d43fc34 --- /dev/null +++ b/template/testdata/zephyr.gotmpl/system-user-assistant-user @@ -0,0 +1,9 @@ +<|system|> +You are a helpful assistant. +<|user|> +Hello, how are you? +<|assistant|> +I'm doing great. How can I help you today? +<|user|> +I'd like to show off how chat templating works! +<|assistant|> diff --git a/template/testdata/zephyr.gotmpl/user b/template/testdata/zephyr.gotmpl/user new file mode 100644 index 000000000..6cefdaa0f --- /dev/null +++ b/template/testdata/zephyr.gotmpl/user @@ -0,0 +1,3 @@ +<|user|> +Hello, how are you? +<|assistant|> diff --git a/template/testdata/zephyr.gotmpl/user-assistant-user b/template/testdata/zephyr.gotmpl/user-assistant-user new file mode 100644 index 000000000..3937b006a --- /dev/null +++ b/template/testdata/zephyr.gotmpl/user-assistant-user @@ -0,0 +1,7 @@ +<|user|> +Hello, how are you? +<|assistant|> +I'm doing great. How can I help you today? +<|user|> +I'd like to show off how chat templating works! +<|assistant|> diff --git a/template/vicuna.gotmpl b/template/vicuna.gotmpl index 174c1a353..2e13e990d 100644 --- a/template/vicuna.gotmpl +++ b/template/vicuna.gotmpl @@ -1,3 +1,14 @@ +{{- if .Messages }} +{{- if .System }}{{ .System }} + +{{ end }} +{{- range .Messages }} +{{- if eq .Role "user" }}USER: {{ .Content }} +{{ else if eq .Role "assistant" }}ASSISTANT: {{ .Content }} +{{ end }} +{{- end }}ASSISTANT: +{{- else }} {{ if .System }}{{ .System }} {{ end }}{{ if .Prompt }}USER: {{ .Prompt }} -{{ end }}ASSISTANT: {{ .Response }} \ No newline at end of file +{{ end }}ASSISTANT: {{ .Response }} +{{- end }} \ No newline at end of file diff --git a/template/zephyr.gotmpl b/template/zephyr.gotmpl index aac0c7a1f..e66688480 100644 --- a/template/zephyr.gotmpl +++ b/template/zephyr.gotmpl @@ -1,6 +1,15 @@ +{{- if .Messages }} +{{- if .System }}<|system|> +{{ .System }} +{{ end }} +{{- range .Messages }}<|{{ .Role }}|> +{{ .Content }} +{{ end }}<|assistant|> +{{ else }} {{ if .System }}<|system|> {{ .System }} {{ end }}{{ if .Prompt }}<|user|> {{ .Prompt }} {{ end }}<|assistant|> -{{ .Response }} \ No newline at end of file +{{ .Response }} +{{- end }} \ No newline at end of file From 5304b765b2bf934070e06412f6617b97a56ae3d2 Mon Sep 17 00:00:00 2001 From: Jeffrey Morgan Date: Fri, 5 Jul 2024 19:34:21 -0400 Subject: [PATCH 035/384] llm: put back old include dir (#5507) * llm: put back old include dir * llm: update link paths for old submodule commits --- llm/llm.go | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/llm/llm.go b/llm/llm.go index fb6d4b5c7..98fe7f09a 100644 --- a/llm/llm.go +++ b/llm/llm.go @@ -1,12 +1,13 @@ package llm -// #cgo CFLAGS: -Illama.cpp/include -Illama.cpp/ggml/include -// #cgo darwin,arm64 LDFLAGS: ${SRCDIR}/build/darwin/arm64_static/src/libllama.a ${SRCDIR}/build/darwin/arm64_static/ggml/src/libggml.a -lstdc++ -framework Accelerate -framework Metal -// #cgo darwin,amd64 LDFLAGS: ${SRCDIR}/build/darwin/x86_64_static/src/libllama.a ${SRCDIR}/build/darwin/x86_64_static/ggml/src/libggml.a -lstdc++ -framework Accelerate -framework Metal -// #cgo windows,amd64 LDFLAGS: ${SRCDIR}/build/windows/amd64_static/src/libllama.a ${SRCDIR}/build/windows/amd64_static/ggml/src/libggml.a -static -lstdc++ -// #cgo windows,arm64 LDFLAGS: ${SRCDIR}/build/windows/arm64_static/src/libllama.a ${SRCDIR}/build/windows/arm64_static/ggml/src/libggml.a -static -lstdc++ -// #cgo linux,amd64 LDFLAGS: ${SRCDIR}/build/linux/x86_64_static/src/libllama.a ${SRCDIR}/build/linux/x86_64_static/ggml/src/libggml.a -lstdc++ -// #cgo linux,arm64 LDFLAGS: ${SRCDIR}/build/linux/arm64_static/src/libllama.a ${SRCDIR}/build/linux/arm64_static/ggml/src/libggml.a -lstdc++ +// #cgo CFLAGS: -Illama.cpp -Illama.cpp/include -Illama.cpp/ggml/include +// #cgo LDFLAGS: -lllama -lggml -lstdc++ +// #cgo darwin,arm64 LDFLAGS: -L${SRCDIR}/build/darwin/arm64_static -L${SRCDIR}/build/darwin/arm64_static/src -L${SRCDIR}/build/darwin/arm64_static/ggml/src -framework Accelerate -framework Metal +// #cgo darwin,amd64 LDFLAGS: -L${SRCDIR}/build/darwin/x86_64_static -L${SRCDIR}/build/darwin/x86_64_static/src -L${SRCDIR}/build/darwin/x86_64_static/ggml/src +// #cgo windows,amd64 LDFLAGS: -L${SRCDIR}/build/windows/amd64_static -L${SRCDIR}/build/windows/amd64_static/src -L${SRCDIR}/build/windows/amd64_static/ggml/src +// #cgo windows,arm64 LDFLAGS: -L${SRCDIR}/build/windows/arm64_static -L${SRCDIR}/build/windows/arm64_static/src -L${SRCDIR}/build/windows/arm64_static/ggml/src +// #cgo linux,amd64 LDFLAGS: -L${SRCDIR}/build/linux/x86_64_static -L${SRCDIR}/build/linux/x86_64_static/src -L${SRCDIR}/build/linux/x86_64_static/ggml/src +// #cgo linux,arm64 LDFLAGS: -L${SRCDIR}/build/linux/arm64_static -L${SRCDIR}/build/linux/arm64_static/src -L${SRCDIR}/build/linux/arm64_static/ggml/src // #include // #include "llama.h" import "C" From 2cc854f8cb5b9670fc53134f8104569c60d535be Mon Sep 17 00:00:00 2001 From: Jeffrey Morgan Date: Fri, 5 Jul 2024 21:48:31 -0400 Subject: [PATCH 036/384] llm: fix missing dylibs by restoring old build behavior on Linux and macOS (#5511) * Revert "fix cmake build (#5505)" This reverts commit 4fd5f3526a116d05cd74cfcc7217d4e6326e1bea. * llm: fix missing dylibs by restoring old build behavior * crlf -> lf --- llm/ext_server/CMakeLists.txt | 28 ++++++++++++---------------- llm/generate/gen_common.sh | 1 - llm/generate/gen_darwin.sh | 6 +++--- llm/generate/gen_linux.sh | 2 +- 4 files changed, 16 insertions(+), 21 deletions(-) diff --git a/llm/ext_server/CMakeLists.txt b/llm/ext_server/CMakeLists.txt index c300244f9..b63f3c0e5 100644 --- a/llm/ext_server/CMakeLists.txt +++ b/llm/ext_server/CMakeLists.txt @@ -1,17 +1,13 @@ - -set(TARGET ollama_llama_server) -option(LLAMA_SERVER_VERBOSE "Build verbose logging option for Server" ON) -include_directories(${CMAKE_CURRENT_SOURCE_DIR}) -add_executable(${TARGET} server.cpp utils.hpp json.hpp httplib.h) -target_compile_definitions(${TARGET} PRIVATE - SERVER_VERBOSE=$ -) -target_link_libraries(${TARGET} PRIVATE ggml llama common llava ${CMAKE_THREAD_LIBS_INIT}) -install(TARGETS ollama_llama_server ggml llama - RUNTIME DESTINATION "${CMAKE_BINARY_DIR}/bin" - LIBRARY DESTINATION "${CMAKE_BINARY_DIR}/bin" - COMPONENT ollama_llama_server) -if (WIN32) - TARGET_LINK_LIBRARIES(${TARGET} PRIVATE ws2_32) -endif() +set(TARGET ollama_llama_server) +option(LLAMA_SERVER_VERBOSE "Build verbose logging option for Server" ON) +include_directories(${CMAKE_CURRENT_SOURCE_DIR}) +add_executable(${TARGET} server.cpp utils.hpp json.hpp httplib.h) +install(TARGETS ${TARGET} RUNTIME) +target_compile_definitions(${TARGET} PRIVATE + SERVER_VERBOSE=$ +) +target_link_libraries(${TARGET} PRIVATE ggml llama common llava ${CMAKE_THREAD_LIBS_INIT}) +if (WIN32) + TARGET_LINK_LIBRARIES(${TARGET} PRIVATE ws2_32) +endif() target_compile_features(${TARGET} PRIVATE cxx_std_11) \ No newline at end of file diff --git a/llm/generate/gen_common.sh b/llm/generate/gen_common.sh index 23feaf99d..da1b06882 100644 --- a/llm/generate/gen_common.sh +++ b/llm/generate/gen_common.sh @@ -81,7 +81,6 @@ apply_patches() { build() { cmake -S ${LLAMACPP_DIR} -B ${BUILD_DIR} ${CMAKE_DEFS} cmake --build ${BUILD_DIR} ${CMAKE_TARGETS} -j8 - cmake --install ${BUILD_DIR} --component ollama_llama_server } compress() { diff --git a/llm/generate/gen_darwin.sh b/llm/generate/gen_darwin.sh index 02577545a..8b4779f95 100755 --- a/llm/generate/gen_darwin.sh +++ b/llm/generate/gen_darwin.sh @@ -18,7 +18,7 @@ sign() { fi } -COMMON_DARWIN_DEFS="-DCMAKE_OSX_DEPLOYMENT_TARGET=11.3 -DLLAMA_METAL_MACOSX_VERSION_MIN=11.3 -DCMAKE_SYSTEM_NAME=Darwin -DGGML_METAL_EMBED_LIBRARY=on -DGGML_OPENMP=off" +COMMON_DARWIN_DEFS="-DBUILD_SHARED_LIBS=off -DCMAKE_OSX_DEPLOYMENT_TARGET=11.3 -DLLAMA_METAL_MACOSX_VERSION_MIN=11.3 -DCMAKE_SYSTEM_NAME=Darwin -DGGML_METAL_EMBED_LIBRARY=on -DGGML_OPENMP=off" case "${GOARCH}" in "amd64") @@ -27,7 +27,7 @@ case "${GOARCH}" in # Static build for linking into the Go binary init_vars CMAKE_TARGETS="--target llama --target ggml" - CMAKE_DEFS="${COMMON_CPU_DEFS} -DBUILD_SHARED_LIBS=off -DGGML_BLAS=off -DGGML_ACCELERATE=off -DGGML_AVX=off -DGGML_AVX2=off -DGGML_AVX512=off -DGGML_FMA=off -DGGML_F16C=off ${CMAKE_DEFS}" + CMAKE_DEFS="${COMMON_CPU_DEFS} -DGGML_BLAS=off -DGGML_ACCELERATE=off -DGGML_AVX=off -DGGML_AVX2=off -DGGML_AVX512=off -DGGML_FMA=off -DGGML_F16C=off ${CMAKE_DEFS}" BUILD_DIR="../build/darwin/${ARCH}_static" echo "Building static library" build @@ -75,7 +75,7 @@ case "${GOARCH}" in # Static build for linking into the Go binary init_vars CMAKE_TARGETS="--target llama --target ggml" - CMAKE_DEFS="-DCMAKE_OSX_DEPLOYMENT_TARGET=11.3 -DCMAKE_SYSTEM_NAME=Darwin -DBUILD_SHARED_LIBS=off -DCMAKE_SYSTEM_PROCESSOR=${ARCH} -DCMAKE_OSX_ARCHITECTURES=${ARCH} ${CMAKE_DEFS}" + CMAKE_DEFS="-DCMAKE_OSX_DEPLOYMENT_TARGET=11.3 -DCMAKE_SYSTEM_NAME=Darwin -DCMAKE_SYSTEM_PROCESSOR=${ARCH} -DCMAKE_OSX_ARCHITECTURES=${ARCH} ${CMAKE_DEFS}" BUILD_DIR="../build/darwin/${ARCH}_static" echo "Building static library" build diff --git a/llm/generate/gen_linux.sh b/llm/generate/gen_linux.sh index c36862520..2bea1c4e6 100755 --- a/llm/generate/gen_linux.sh +++ b/llm/generate/gen_linux.sh @@ -51,7 +51,7 @@ if [ -z "${CUDACXX}" ]; then export CUDACXX=$(command -v nvcc) fi fi -COMMON_CMAKE_DEFS="-DCMAKE_POSITION_INDEPENDENT_CODE=on -DGGML_NATIVE=off -DGGML_AVX=on -DGGML_AVX2=off -DGGML_AVX512=off -DGGML_FMA=off -DGGML_F16C=off -DGGML_OPENMP=off" +COMMON_CMAKE_DEFS="-DBUILD_SHARED_LIBS=off -DCMAKE_POSITION_INDEPENDENT_CODE=on -DGGML_NATIVE=off -DGGML_AVX=on -DGGML_AVX2=off -DGGML_AVX512=off -DGGML_FMA=off -DGGML_F16C=off -DGGML_OPENMP=off" source $(dirname $0)/gen_common.sh init_vars git_module_setup From e0348d3fe8042b7e378a7cbcee95d17d20a14017 Mon Sep 17 00:00:00 2001 From: Jeffrey Morgan Date: Fri, 5 Jul 2024 22:42:42 -0400 Subject: [PATCH 037/384] llm: add `COMMON_DARWIN_DEFS` to arm static build (#5513) --- llm/generate/gen_darwin.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/llm/generate/gen_darwin.sh b/llm/generate/gen_darwin.sh index 8b4779f95..6c0b62cb7 100755 --- a/llm/generate/gen_darwin.sh +++ b/llm/generate/gen_darwin.sh @@ -75,7 +75,7 @@ case "${GOARCH}" in # Static build for linking into the Go binary init_vars CMAKE_TARGETS="--target llama --target ggml" - CMAKE_DEFS="-DCMAKE_OSX_DEPLOYMENT_TARGET=11.3 -DCMAKE_SYSTEM_NAME=Darwin -DCMAKE_SYSTEM_PROCESSOR=${ARCH} -DCMAKE_OSX_ARCHITECTURES=${ARCH} ${CMAKE_DEFS}" + CMAKE_DEFS="${COMMON_DARWIN_DEFS} -DCMAKE_OSX_DEPLOYMENT_TARGET=11.3 -DCMAKE_SYSTEM_NAME=Darwin -DCMAKE_SYSTEM_PROCESSOR=${ARCH} -DCMAKE_OSX_ARCHITECTURES=${ARCH} ${CMAKE_DEFS}" BUILD_DIR="../build/darwin/${ARCH}_static" echo "Building static library" build From 9ae146993e9ec834b95d038df1eecac68a744f18 Mon Sep 17 00:00:00 2001 From: jmorganca Date: Sat, 6 Jul 2024 03:27:05 -0400 Subject: [PATCH 038/384] llm: add `GGML_STATIC` flag to windows static lib --- llm/generate/gen_windows.ps1 | 1 + 1 file changed, 1 insertion(+) diff --git a/llm/generate/gen_windows.ps1 b/llm/generate/gen_windows.ps1 index 5c6943502..123c44cc1 100644 --- a/llm/generate/gen_windows.ps1 +++ b/llm/generate/gen_windows.ps1 @@ -204,6 +204,7 @@ function build_static() { "-DCMAKE_C_COMPILER=gcc.exe", "-DCMAKE_CXX_COMPILER=g++.exe", "-DBUILD_SHARED_LIBS=off", + "-DGGML_STATIC=on", "-DGGML_NATIVE=off", "-DGGML_AVX=off", "-DGGML_AVX2=off", From f1a379aa566f7a9fefb2a64ac35faf34d9c00812 Mon Sep 17 00:00:00 2001 From: jmorganca Date: Sat, 6 Jul 2024 12:54:02 -0400 Subject: [PATCH 039/384] llm: statically link pthread and stdc++ dependencies in windows build --- llm/generate/gen_windows.ps1 | 1 - llm/llm.go | 3 ++- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/llm/generate/gen_windows.ps1 b/llm/generate/gen_windows.ps1 index 123c44cc1..5c6943502 100644 --- a/llm/generate/gen_windows.ps1 +++ b/llm/generate/gen_windows.ps1 @@ -204,7 +204,6 @@ function build_static() { "-DCMAKE_C_COMPILER=gcc.exe", "-DCMAKE_CXX_COMPILER=g++.exe", "-DBUILD_SHARED_LIBS=off", - "-DGGML_STATIC=on", "-DGGML_NATIVE=off", "-DGGML_AVX=off", "-DGGML_AVX2=off", diff --git a/llm/llm.go b/llm/llm.go index 98fe7f09a..3cd162e0c 100644 --- a/llm/llm.go +++ b/llm/llm.go @@ -1,7 +1,8 @@ package llm // #cgo CFLAGS: -Illama.cpp -Illama.cpp/include -Illama.cpp/ggml/include -// #cgo LDFLAGS: -lllama -lggml -lstdc++ +// #cgo LDFLAGS: -lllama -lggml -lstdc++ -lpthread +// #cgo windows LDFLAGS: -static-libstdc++ -static-libgcc -static // #cgo darwin,arm64 LDFLAGS: -L${SRCDIR}/build/darwin/arm64_static -L${SRCDIR}/build/darwin/arm64_static/src -L${SRCDIR}/build/darwin/arm64_static/ggml/src -framework Accelerate -framework Metal // #cgo darwin,amd64 LDFLAGS: -L${SRCDIR}/build/darwin/x86_64_static -L${SRCDIR}/build/darwin/x86_64_static/src -L${SRCDIR}/build/darwin/x86_64_static/ggml/src // #cgo windows,amd64 LDFLAGS: -L${SRCDIR}/build/windows/amd64_static -L${SRCDIR}/build/windows/amd64_static/src -L${SRCDIR}/build/windows/amd64_static/ggml/src From 5796bfc4013f4ebe26cdbf13554332a25c405027 Mon Sep 17 00:00:00 2001 From: jmorganca Date: Sat, 6 Jul 2024 14:06:20 -0400 Subject: [PATCH 040/384] llm: only statically link libstdc++ --- .github/workflows/release.yaml | 4 ++++ llm/llm.go | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 61ca3c433..1042c6845 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -304,6 +304,10 @@ jobs: write-host "Installing plugin" & "${env:RUNNER_TEMP}\plugin\*\kmscng.msi" /quiet write-host "plugin installed" + - name: remove unwanted mingw dll.a files + run: | + Remove-Item "C:\mingw64\x86_64-w64-mingw32\lib\libpthread.dll.a" + Remove-Item "C:\mingw64\x86_64-w64-mingw32\lib\libwinpthread.dll.a" - uses: actions/setup-go@v5 with: go-version-file: go.mod diff --git a/llm/llm.go b/llm/llm.go index 3cd162e0c..ac6a52490 100644 --- a/llm/llm.go +++ b/llm/llm.go @@ -1,8 +1,8 @@ package llm // #cgo CFLAGS: -Illama.cpp -Illama.cpp/include -Illama.cpp/ggml/include +// #cgo windows LDFLAGS: -static-libstdc++ // #cgo LDFLAGS: -lllama -lggml -lstdc++ -lpthread -// #cgo windows LDFLAGS: -static-libstdc++ -static-libgcc -static // #cgo darwin,arm64 LDFLAGS: -L${SRCDIR}/build/darwin/arm64_static -L${SRCDIR}/build/darwin/arm64_static/src -L${SRCDIR}/build/darwin/arm64_static/ggml/src -framework Accelerate -framework Metal // #cgo darwin,amd64 LDFLAGS: -L${SRCDIR}/build/darwin/x86_64_static -L${SRCDIR}/build/darwin/x86_64_static/src -L${SRCDIR}/build/darwin/x86_64_static/ggml/src // #cgo windows,amd64 LDFLAGS: -L${SRCDIR}/build/windows/amd64_static -L${SRCDIR}/build/windows/amd64_static/src -L${SRCDIR}/build/windows/amd64_static/ggml/src From 6cea0360276e5fc7e2fecbe0cadf89cc72615279 Mon Sep 17 00:00:00 2001 From: jmorganca Date: Sat, 6 Jul 2024 15:10:48 -0400 Subject: [PATCH 041/384] Revert "llm: only statically link libstdc++" This reverts commit 5796bfc4013f4ebe26cdbf13554332a25c405027. --- .github/workflows/release.yaml | 4 ---- llm/llm.go | 2 +- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 1042c6845..61ca3c433 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -304,10 +304,6 @@ jobs: write-host "Installing plugin" & "${env:RUNNER_TEMP}\plugin\*\kmscng.msi" /quiet write-host "plugin installed" - - name: remove unwanted mingw dll.a files - run: | - Remove-Item "C:\mingw64\x86_64-w64-mingw32\lib\libpthread.dll.a" - Remove-Item "C:\mingw64\x86_64-w64-mingw32\lib\libwinpthread.dll.a" - uses: actions/setup-go@v5 with: go-version-file: go.mod diff --git a/llm/llm.go b/llm/llm.go index ac6a52490..3cd162e0c 100644 --- a/llm/llm.go +++ b/llm/llm.go @@ -1,8 +1,8 @@ package llm // #cgo CFLAGS: -Illama.cpp -Illama.cpp/include -Illama.cpp/ggml/include -// #cgo windows LDFLAGS: -static-libstdc++ // #cgo LDFLAGS: -lllama -lggml -lstdc++ -lpthread +// #cgo windows LDFLAGS: -static-libstdc++ -static-libgcc -static // #cgo darwin,arm64 LDFLAGS: -L${SRCDIR}/build/darwin/arm64_static -L${SRCDIR}/build/darwin/arm64_static/src -L${SRCDIR}/build/darwin/arm64_static/ggml/src -framework Accelerate -framework Metal // #cgo darwin,amd64 LDFLAGS: -L${SRCDIR}/build/darwin/x86_64_static -L${SRCDIR}/build/darwin/x86_64_static/src -L${SRCDIR}/build/darwin/x86_64_static/ggml/src // #cgo windows,amd64 LDFLAGS: -L${SRCDIR}/build/windows/amd64_static -L${SRCDIR}/build/windows/amd64_static/src -L${SRCDIR}/build/windows/amd64_static/ggml/src From a08f20d910194edff79d45315330a088fda3f136 Mon Sep 17 00:00:00 2001 From: jmorganca Date: Sat, 6 Jul 2024 15:21:15 -0400 Subject: [PATCH 042/384] release: remove unwanted mingw dll.a files --- .github/workflows/release.yaml | 5 +++++ llm/llm.go | 1 - 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 61ca3c433..d1faf9f5b 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -85,6 +85,11 @@ jobs: write-host "Installing plugin" & "${env:RUNNER_TEMP}\plugin\*\kmscng.msi" /quiet write-host "plugin installed" + - name: remove unwanted mingw dll.a files + run: | + Get-ChildItem -Path "C:\mingw64" -Recurse -Filter "libpthread.dll.a" -File | Remove-Item -Force + Get-ChildItem -Path "C:\mingw64" -Recurse -Filter "libwinpthread.dll.a" -File | Remove-Item -Force + Get-ChildItem -Path "C:\mingw64" -Recurse -Filter "libstdc++.dll.a" -File | Remove-Item -Force - uses: actions/setup-go@v5 with: go-version-file: go.mod diff --git a/llm/llm.go b/llm/llm.go index 3cd162e0c..88c0258d6 100644 --- a/llm/llm.go +++ b/llm/llm.go @@ -2,7 +2,6 @@ package llm // #cgo CFLAGS: -Illama.cpp -Illama.cpp/include -Illama.cpp/ggml/include // #cgo LDFLAGS: -lllama -lggml -lstdc++ -lpthread -// #cgo windows LDFLAGS: -static-libstdc++ -static-libgcc -static // #cgo darwin,arm64 LDFLAGS: -L${SRCDIR}/build/darwin/arm64_static -L${SRCDIR}/build/darwin/arm64_static/src -L${SRCDIR}/build/darwin/arm64_static/ggml/src -framework Accelerate -framework Metal // #cgo darwin,amd64 LDFLAGS: -L${SRCDIR}/build/darwin/x86_64_static -L${SRCDIR}/build/darwin/x86_64_static/src -L${SRCDIR}/build/darwin/x86_64_static/ggml/src // #cgo windows,amd64 LDFLAGS: -L${SRCDIR}/build/windows/amd64_static -L${SRCDIR}/build/windows/amd64_static/src -L${SRCDIR}/build/windows/amd64_static/ggml/src From c12f1c5b99c9d9f9388f464aa77063987fdb8f0f Mon Sep 17 00:00:00 2001 From: jmorganca Date: Sat, 6 Jul 2024 16:12:29 -0400 Subject: [PATCH 043/384] release: move mingw library cleanup to correct job --- .github/workflows/release.yaml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index d1faf9f5b..0005c69d3 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -85,11 +85,6 @@ jobs: write-host "Installing plugin" & "${env:RUNNER_TEMP}\plugin\*\kmscng.msi" /quiet write-host "plugin installed" - - name: remove unwanted mingw dll.a files - run: | - Get-ChildItem -Path "C:\mingw64" -Recurse -Filter "libpthread.dll.a" -File | Remove-Item -Force - Get-ChildItem -Path "C:\mingw64" -Recurse -Filter "libwinpthread.dll.a" -File | Remove-Item -Force - Get-ChildItem -Path "C:\mingw64" -Recurse -Filter "libstdc++.dll.a" -File | Remove-Item -Force - uses: actions/setup-go@v5 with: go-version-file: go.mod @@ -309,6 +304,11 @@ jobs: write-host "Installing plugin" & "${env:RUNNER_TEMP}\plugin\*\kmscng.msi" /quiet write-host "plugin installed" + - name: remove unwanted mingw dll.a files + run: | + Get-ChildItem -Path "C:\mingw64" -Recurse -Filter "libpthread.dll.a" -File | Remove-Item -Force + Get-ChildItem -Path "C:\mingw64" -Recurse -Filter "libwinpthread.dll.a" -File | Remove-Item -Force + Get-ChildItem -Path "C:\mingw64" -Recurse -Filter "libstdc++.dll.a" -File | Remove-Item -Force - uses: actions/setup-go@v5 with: go-version-file: go.mod From 4607c706413f1354d0e762d25a9a0a933edc14ec Mon Sep 17 00:00:00 2001 From: Jeffrey Morgan Date: Sat, 6 Jul 2024 18:58:16 -0400 Subject: [PATCH 044/384] llm: add `-DBUILD_SHARED_LIBS=off` to common cpu cmake flags (#5520) --- llm/generate/gen_linux.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/llm/generate/gen_linux.sh b/llm/generate/gen_linux.sh index 2bea1c4e6..d3e2d13ba 100755 --- a/llm/generate/gen_linux.sh +++ b/llm/generate/gen_linux.sh @@ -77,7 +77,7 @@ if [ -z "${OLLAMA_SKIP_CPU_GENERATE}" ]; then if [ -n "${OLLAMA_CUSTOM_CPU_DEFS}" ]; then init_vars echo "OLLAMA_CUSTOM_CPU_DEFS=\"${OLLAMA_CUSTOM_CPU_DEFS}\"" - CMAKE_DEFS="${OLLAMA_CUSTOM_CPU_DEFS} -DCMAKE_POSITION_INDEPENDENT_CODE=on ${CMAKE_DEFS}" + CMAKE_DEFS="${OLLAMA_CUSTOM_CPU_DEFS} -DBUILD_SHARED_LIBS=off -DCMAKE_POSITION_INDEPENDENT_CODE=on ${CMAKE_DEFS}" BUILD_DIR="../build/linux/${ARCH}/cpu" echo "Building custom CPU" build @@ -93,7 +93,7 @@ if [ -z "${OLLAMA_SKIP_CPU_GENERATE}" ]; then # -DGGML_AVX512_VBMI -- 2018 Intel Cannon Lake # -DGGML_AVX512_VNNI -- 2021 Intel Alder Lake - COMMON_CPU_DEFS="-DCMAKE_POSITION_INDEPENDENT_CODE=on -DGGML_NATIVE=off -DGGML_OPENMP=off" + COMMON_CPU_DEFS="-DBUILD_SHARED_LIBS=off -DCMAKE_POSITION_INDEPENDENT_CODE=on -DGGML_NATIVE=off -DGGML_OPENMP=off" if [ -z "${OLLAMA_CPU_TARGET}" -o "${OLLAMA_CPU_TARGET}" = "cpu" ]; then # # CPU first for the default library, set up as lowest common denominator for maximum compatibility (including Rosetta) From f8241bfba384cf8c888847dc44b73d7f43a42d82 Mon Sep 17 00:00:00 2001 From: Jeffrey Morgan Date: Sat, 6 Jul 2024 19:35:04 -0400 Subject: [PATCH 045/384] gpu: report system free memory instead of 0 (#5521) --- gpu/gpu_darwin.go | 2 +- gpu/gpu_info_darwin.h | 1 + gpu/gpu_info_darwin.m | 26 ++++++++++++++++++++++++-- 3 files changed, 26 insertions(+), 3 deletions(-) diff --git a/gpu/gpu_darwin.go b/gpu/gpu_darwin.go index f26d23c12..39d8fcf89 100644 --- a/gpu/gpu_darwin.go +++ b/gpu/gpu_darwin.go @@ -56,7 +56,7 @@ func GetCPUInfo() GpuInfoList { func GetCPUMem() (memInfo, error) { return memInfo{ TotalMemory: uint64(C.getPhysicalMemory()), - FreeMemory: 0, + FreeMemory: uint64(C.getFreeMemory()), }, nil } diff --git a/gpu/gpu_info_darwin.h b/gpu/gpu_info_darwin.h index 3edca237c..415e7922d 100644 --- a/gpu/gpu_info_darwin.h +++ b/gpu/gpu_info_darwin.h @@ -2,3 +2,4 @@ #include uint64_t getRecommendedMaxVRAM(); uint64_t getPhysicalMemory(); +uint64_t getFreeMemory(); diff --git a/gpu/gpu_info_darwin.m b/gpu/gpu_info_darwin.m index a145ac076..5ca139e0b 100644 --- a/gpu/gpu_info_darwin.m +++ b/gpu/gpu_info_darwin.m @@ -1,4 +1,5 @@ -// go:build darwin +#import +#import #include "gpu_info_darwin.h" uint64_t getRecommendedMaxVRAM() { @@ -8,6 +9,27 @@ uint64_t getRecommendedMaxVRAM() { return result; } +// getPhysicalMemory returns the total physical memory in bytes uint64_t getPhysicalMemory() { - return [[NSProcessInfo processInfo] physicalMemory]; + return [NSProcessInfo processInfo].physicalMemory; +} + +// getFreeMemory returns the total free memory in bytes, including inactive +// memory that can be reclaimed by the system. +uint64_t getFreeMemory() { + mach_port_t host_port = mach_host_self(); + mach_msg_type_number_t host_size = sizeof(vm_statistics64_data_t) / sizeof(integer_t); + vm_size_t pagesize; + vm_statistics64_data_t vm_stat; + + host_page_size(host_port, &pagesize); + if (host_statistics64(host_port, HOST_VM_INFO64, (host_info64_t)&vm_stat, &host_size) != KERN_SUCCESS) { + return 0; + } + + uint64_t free_memory = (uint64_t)vm_stat.free_count * pagesize; + free_memory += (uint64_t)vm_stat.speculative_count * pagesize; + free_memory += (uint64_t)vm_stat.inactive_count * pagesize; + + return free_memory; } From 0ee87615c74c69d8fbc3cad8f3ea5a2364b1a876 Mon Sep 17 00:00:00 2001 From: Jeffrey Morgan Date: Sat, 6 Jul 2024 22:01:52 -0400 Subject: [PATCH 046/384] sched: don't error if paging to disk on Windows and macOS (#5523) --- server/sched.go | 37 ++++++++++++++++++++++++------------- 1 file changed, 24 insertions(+), 13 deletions(-) diff --git a/server/sched.go b/server/sched.go index 8c054c6b4..9dff2ae07 100644 --- a/server/sched.go +++ b/server/sched.go @@ -197,25 +197,36 @@ func (s *Scheduler) processPending(ctx context.Context) { break } - // Block attempting to load a model larger than system memory + GPU memory estimate := llm.EstimateGPULayers(gpus, ggml, pending.model.ProjectorPaths, pending.opts) maxSize := systemMem.FreeMemory - for _, gpu := range gpus { - if gpu.Library == "cpu" { - continue - } - if loadedCount == 0 { - // If no other models are loaded, set the limit based on what's available - maxSize += gpu.FreeMemory - } else { - // Other models could be unloaded, favor total memory for limit - maxSize += gpu.TotalMemory + + // Add available GPU memory to the total pool + // macOS hardware has unified memory so don't double count + if runtime.GOOS != "darwin" { + for _, gpu := range gpus { + if gpu.Library == "cpu" { + continue + } + if loadedCount == 0 { + // If no other models are loaded, set the limit based on what's available + maxSize += gpu.FreeMemory + } else { + // Other models could be unloaded, favor total memory for limit + maxSize += gpu.TotalMemory + } } } + + // Block attempting to load a model larger than system memory + GPU memory if estimate.TotalSize > maxSize { slog.Warn("model request too large for system", "requested", format.HumanBytes2(estimate.TotalSize), "system", format.HumanBytes2(maxSize)) - pending.errCh <- fmt.Errorf("requested model (%s) is too large for this system (%s)", format.HumanBytes2(estimate.TotalSize), format.HumanBytes2(maxSize)) - break + + // Linux will crash if over-allocating memory - return an error to the user. + // TODO (jmorganca): add reasonable upper limits for darwin and windows as well + if runtime.GOOS == "linux" { + pending.errCh <- fmt.Errorf("requested model (%s) is too large for this system (%s)", format.HumanBytes2(estimate.TotalSize), format.HumanBytes2(maxSize)) + break + } } // Evaluate if the model will fit in the available system memory, or if we should unload a model first From 0e09c380fcae8b81db3c3447d70d721cfad00dbd Mon Sep 17 00:00:00 2001 From: Jeffrey Morgan Date: Sun, 7 Jul 2024 12:38:04 -0400 Subject: [PATCH 047/384] llm: print caching notices in debug only (#5533) --- llm/ext_server/server.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/llm/ext_server/server.cpp b/llm/ext_server/server.cpp index 00a15b4a3..7ae58e382 100644 --- a/llm/ext_server/server.cpp +++ b/llm/ext_server/server.cpp @@ -1413,7 +1413,7 @@ struct llama_server_context return get_slot(-1); } - LOG_INFO("slot with common prefix found", {{ + LOG_DEBUG("slot with common prefix found", {{ "slot_id", slot->id, "characters", longest }}); From 571dc61955ced560a45e9d32b1cd2a52d9803c8c Mon Sep 17 00:00:00 2001 From: Jeffrey Morgan Date: Sun, 7 Jul 2024 13:03:09 -0400 Subject: [PATCH 048/384] Update llama.cpp submodule to `a8db2a9c` (#5530) --- llm/llama.cpp | 2 +- llm/patches/05-default-pretokenizer.diff | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/llm/llama.cpp b/llm/llama.cpp index d7fd29fff..a8db2a9ce 160000 --- a/llm/llama.cpp +++ b/llm/llama.cpp @@ -1 +1 @@ -Subproject commit d7fd29fff16456ce9c3a23fd2d09a66256b05aff +Subproject commit a8db2a9ce64cd4417f6a312ab61858f17f0f8584 diff --git a/llm/patches/05-default-pretokenizer.diff b/llm/patches/05-default-pretokenizer.diff index f4eaced72..341a6f590 100644 --- a/llm/patches/05-default-pretokenizer.diff +++ b/llm/patches/05-default-pretokenizer.diff @@ -1,11 +1,11 @@ diff --git a/src/llama.cpp b/src/llama.cpp -index 73f52435..2b81b4bd 100644 +index 2b9ace28..172640e2 100644 --- a/src/llama.cpp +++ b/src/llama.cpp -@@ -5092,16 +5092,7 @@ static void llm_load_vocab( - - // for now, only BPE models have pre-tokenizers +@@ -5357,16 +5357,7 @@ static void llm_load_vocab( if (vocab.type == LLAMA_VOCAB_TYPE_BPE) { + vocab.tokenizer_add_space_prefix = false; + vocab.tokenizer_clean_spaces = true; - if (tokenizer_pre.empty()) { - LLAMA_LOG_WARN("%s: missing pre-tokenizer type, using: 'default'\n", __func__); - LLAMA_LOG_WARN("%s: \n", __func__); @@ -20,7 +20,7 @@ index 73f52435..2b81b4bd 100644 vocab.type_pre = LLAMA_VOCAB_PRE_TYPE_DEFAULT; } else if ( tokenizer_pre == "llama3" || -@@ -5164,7 +5155,8 @@ static void llm_load_vocab( +@@ -5439,7 +5430,8 @@ static void llm_load_vocab( tokenizer_pre == "jais") { vocab.type_pre = LLAMA_VOCAB_PRE_TYPE_JAIS; } else { From d8def1ff9432ef60d1067e5e6dde0d700dd95021 Mon Sep 17 00:00:00 2001 From: Jeffrey Morgan Date: Sun, 7 Jul 2024 13:41:51 -0400 Subject: [PATCH 049/384] llm: allow gemma 2 to context shift (#5534) --- llm/ext_server/server.cpp | 29 +---------------------------- 1 file changed, 1 insertion(+), 28 deletions(-) diff --git a/llm/ext_server/server.cpp b/llm/ext_server/server.cpp index 7ae58e382..0ef3956ec 100644 --- a/llm/ext_server/server.cpp +++ b/llm/ext_server/server.cpp @@ -1688,22 +1688,8 @@ struct llama_server_context } slot.params.n_keep = std::min(slot.n_ctx - 4, slot.params.n_keep); - char buf[256]; - llama_model_meta_val_str(model, "general.architecture", buf, 256); - bool gemma2 = strcmp(buf, "gemma2") == 0; - - int32_t truncate_at = slot.n_ctx; - - // truncate at 2/3 of the context length for gemma2 models - // as they do not support context shifts (from the sliding window implementation). - // this way, prompts that almost fit the context length can still generate a full - // response without a sudden stop from hitting the context limit - if (gemma2) { - truncate_at = 2 * slot.n_ctx / 3; - } - // if input prompt is too big, truncate it, if group attention self-extend is disabled - if (slot.ga_n == 1 && slot.n_prompt_tokens >= truncate_at) + if (slot.ga_n == 1 && slot.n_prompt_tokens >= slot.n_ctx) { const int n_left = slot.n_ctx - slot.params.n_keep; const int n_shift = n_left / 2; @@ -1731,19 +1717,6 @@ struct llama_server_context GGML_ASSERT(slot.n_prompt_tokens < slot.n_ctx); } - // Models with sliding window attention do not work with context shifts, so - // limit their prediction to the context length - if (gemma2) { - int32_t limit = slot.n_ctx - slot.n_prompt_tokens; - slot.n_predict = limit; - slot.params.n_predict = limit; - LOG_INFO("model does not support sliding window, limiting generation", { - {"n_ctx", slot.n_ctx}, - {"n_prompt_tokens", slot.n_prompt_tokens}, - {"n_predict", slot.n_predict} - }); - } - if (!slot.params.cache_prompt) { llama_sampling_reset(slot.ctx_sampling); From 53da2c69654769c0c086af695722e1d9b9ee6ecc Mon Sep 17 00:00:00 2001 From: Jeffrey Morgan Date: Sun, 7 Jul 2024 14:32:05 -0400 Subject: [PATCH 050/384] llm: remove ambiguous comment when putting upper limit on predictions to avoid infinite generation (#5535) --- llm/server.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/llm/server.go b/llm/server.go index 206f9e391..54fad92ce 100644 --- a/llm/server.go +++ b/llm/server.go @@ -699,10 +699,9 @@ func (s *llmServer) Completion(ctx context.Context, req CompletionRequest, fn fu } defer s.sem.Release(1) - // only allow maximum 10 "context shifts" to avoid infinite generation + // put an upper limit on num_predict to avoid the model running on forever if req.Options.NumPredict < 0 || req.Options.NumPredict > 10*s.options.NumCtx { req.Options.NumPredict = 10 * s.options.NumCtx - slog.Debug("setting token limit to 10x num_ctx", "num_ctx", s.options.NumCtx, "num_predict", req.Options.NumPredict) } request := map[string]any{ From 0bacb300071ba4baa928075b142633f2e85281ab Mon Sep 17 00:00:00 2001 From: Daniel Hiltgen Date: Fri, 5 Jul 2024 12:46:28 -0700 Subject: [PATCH 051/384] Workaround broken ROCm p2p copy Enable the build flag for llama.cpp to use CPU copy for multi-GPU scenarios. --- llm/generate/gen_linux.sh | 2 +- llm/generate/gen_windows.ps1 | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/llm/generate/gen_linux.sh b/llm/generate/gen_linux.sh index d3e2d13ba..304eadbd9 100755 --- a/llm/generate/gen_linux.sh +++ b/llm/generate/gen_linux.sh @@ -254,7 +254,7 @@ if [ -z "${OLLAMA_SKIP_ROCM_GENERATE}" -a -d "${ROCM_PATH}" ]; then ROCM_VARIANT=_v$(ls ${ROCM_PATH}/lib/librocblas.so.*.*.????? | cut -f5 -d. || true) fi init_vars - CMAKE_DEFS="${COMMON_CMAKE_DEFS} ${CMAKE_DEFS} -DGGML_HIPBLAS=on -DCMAKE_C_COMPILER=$ROCM_PATH/llvm/bin/clang -DCMAKE_CXX_COMPILER=$ROCM_PATH/llvm/bin/clang++ -DAMDGPU_TARGETS=$(amdGPUs) -DGPU_TARGETS=$(amdGPUs)" + CMAKE_DEFS="${COMMON_CMAKE_DEFS} ${CMAKE_DEFS} -DGGML_HIPBLAS=on -DLLAMA_CUDA_NO_PEER_COPY=on -DCMAKE_C_COMPILER=$ROCM_PATH/llvm/bin/clang -DCMAKE_CXX_COMPILER=$ROCM_PATH/llvm/bin/clang++ -DAMDGPU_TARGETS=$(amdGPUs) -DGPU_TARGETS=$(amdGPUs)" # Users building from source can tune the exact flags we pass to cmake for configuring llama.cpp if [ -n "${OLLAMA_CUSTOM_ROCM_DEFS}" ]; then echo "OLLAMA_CUSTOM_ROCM_DEFS=\"${OLLAMA_CUSTOM_ROCM_DEFS}\"" diff --git a/llm/generate/gen_windows.ps1 b/llm/generate/gen_windows.ps1 index 5c6943502..26bc4fa3e 100644 --- a/llm/generate/gen_windows.ps1 +++ b/llm/generate/gen_windows.ps1 @@ -366,6 +366,7 @@ function build_rocm() { "-DCMAKE_C_COMPILER=clang.exe", "-DCMAKE_CXX_COMPILER=clang++.exe", "-DGGML_HIPBLAS=on", + "-DLLAMA_CUDA_NO_PEER_COPY=on", "-DHIP_PLATFORM=amd", "-DGGML_AVX=on", "-DGGML_AVX2=off", From b44320db1302baea88e2f318d984218c68faa5f1 Mon Sep 17 00:00:00 2001 From: Daniel Hiltgen Date: Mon, 8 Jul 2024 18:24:21 -0700 Subject: [PATCH 052/384] Bundle missing CRT libraries Some users are experienging runner startup errors due to not having these msvc redist libraries on their host --- scripts/build_windows.ps1 | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/scripts/build_windows.ps1 b/scripts/build_windows.ps1 index b3991ce1f..edc737593 100644 --- a/scripts/build_windows.ps1 +++ b/scripts/build_windows.ps1 @@ -107,9 +107,12 @@ function gatherDependencies() { # TODO - this varies based on host build system and MSVC version - drive from dumpbin output # currently works for Win11 + MSVC 2019 + Cuda V11 - cp "${env:VCToolsRedistDir}\x64\Microsoft.VC*.CRT\msvcp140.dll" "${script:DEPS_DIR}\ollama_runners\" + cp "${env:VCToolsRedistDir}\x64\Microsoft.VC*.CRT\msvcp140*.dll" "${script:DEPS_DIR}\ollama_runners\" cp "${env:VCToolsRedistDir}\x64\Microsoft.VC*.CRT\vcruntime140.dll" "${script:DEPS_DIR}\ollama_runners\" cp "${env:VCToolsRedistDir}\x64\Microsoft.VC*.CRT\vcruntime140_1.dll" "${script:DEPS_DIR}\ollama_runners\" + foreach ($part in $("runtime", "stdio", "filesystem", "math", "convert", "heap", "string", "time", "locale", "environment")) { + cp "$env:VCToolsRedistDir\..\..\..\Tools\Llvm\x64\bin\api-ms-win-crt-${part}*.dll" "${script:DEPS_DIR}\ollama_runners\" + } cp "${script:SRC_DIR}\app\ollama_welcome.ps1" "${script:SRC_DIR}\dist\" From e4ff73297db2f53f1ea4b603df5670c5bde6a944 Mon Sep 17 00:00:00 2001 From: Jeffrey Morgan Date: Mon, 8 Jul 2024 22:32:15 -0700 Subject: [PATCH 053/384] server: fix model reloads when setting `OLLAMA_NUM_PARALLEL` (#5560) * server: fix unneeded model reloads when setting `OLLAMA_NUM_PARALLEL` * remove whitespace change * undo some changes --- server/sched.go | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/server/sched.go b/server/sched.go index 9dff2ae07..48047bfec 100644 --- a/server/sched.go +++ b/server/sched.go @@ -133,10 +133,6 @@ func (s *Scheduler) processPending(ctx context.Context) { numParallel = 1 slog.Warn("multimodal models don't support parallel requests yet") } - // Keep NumCtx and numParallel in sync - if numParallel > 1 { - pending.opts.NumCtx = pending.origNumCtx * numParallel - } for { cpus := s.getCpuFn() @@ -234,9 +230,10 @@ func (s *Scheduler) processPending(ctx context.Context) { // simplifying assumption of defaultParallel when in CPU mode if numParallel <= 0 { numParallel = defaultParallel - pending.opts.NumCtx = pending.origNumCtx * numParallel } + pending.opts.NumCtx = pending.origNumCtx * numParallel + if loadedCount == 0 { slog.Debug("cpu mode with first model, loading") s.loadFn(pending, ggml, gpus, numParallel) From b51e3b63ac7bc995e99f3a8f7c1b507a1f8fb5d9 Mon Sep 17 00:00:00 2001 From: Daniel Hiltgen Date: Tue, 9 Jul 2024 11:17:44 -0700 Subject: [PATCH 054/384] Statically link c++ and thread lib This makes sure we statically link the c++ and thread library on windows to avoid unnecessary runtime dependencies on non-standard DLLs --- .github/workflows/release.yaml | 5 ----- llm/llm.go | 4 ++-- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 0005c69d3..61ca3c433 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -304,11 +304,6 @@ jobs: write-host "Installing plugin" & "${env:RUNNER_TEMP}\plugin\*\kmscng.msi" /quiet write-host "plugin installed" - - name: remove unwanted mingw dll.a files - run: | - Get-ChildItem -Path "C:\mingw64" -Recurse -Filter "libpthread.dll.a" -File | Remove-Item -Force - Get-ChildItem -Path "C:\mingw64" -Recurse -Filter "libwinpthread.dll.a" -File | Remove-Item -Force - Get-ChildItem -Path "C:\mingw64" -Recurse -Filter "libstdc++.dll.a" -File | Remove-Item -Force - uses: actions/setup-go@v5 with: go-version-file: go.mod diff --git a/llm/llm.go b/llm/llm.go index 88c0258d6..f2a5e557a 100644 --- a/llm/llm.go +++ b/llm/llm.go @@ -4,8 +4,8 @@ package llm // #cgo LDFLAGS: -lllama -lggml -lstdc++ -lpthread // #cgo darwin,arm64 LDFLAGS: -L${SRCDIR}/build/darwin/arm64_static -L${SRCDIR}/build/darwin/arm64_static/src -L${SRCDIR}/build/darwin/arm64_static/ggml/src -framework Accelerate -framework Metal // #cgo darwin,amd64 LDFLAGS: -L${SRCDIR}/build/darwin/x86_64_static -L${SRCDIR}/build/darwin/x86_64_static/src -L${SRCDIR}/build/darwin/x86_64_static/ggml/src -// #cgo windows,amd64 LDFLAGS: -L${SRCDIR}/build/windows/amd64_static -L${SRCDIR}/build/windows/amd64_static/src -L${SRCDIR}/build/windows/amd64_static/ggml/src -// #cgo windows,arm64 LDFLAGS: -L${SRCDIR}/build/windows/arm64_static -L${SRCDIR}/build/windows/arm64_static/src -L${SRCDIR}/build/windows/arm64_static/ggml/src +// #cgo windows,amd64 LDFLAGS: -static-libstdc++ -static-libgcc -static -L${SRCDIR}/build/windows/amd64_static -L${SRCDIR}/build/windows/amd64_static/src -L${SRCDIR}/build/windows/amd64_static/ggml/src +// #cgo windows,arm64 LDFLAGS: -static-libstdc++ -static-libgcc -static -L${SRCDIR}/build/windows/arm64_static -L${SRCDIR}/build/windows/arm64_static/src -L${SRCDIR}/build/windows/arm64_static/ggml/src // #cgo linux,amd64 LDFLAGS: -L${SRCDIR}/build/linux/x86_64_static -L${SRCDIR}/build/linux/x86_64_static/src -L${SRCDIR}/build/linux/x86_64_static/ggml/src // #cgo linux,arm64 LDFLAGS: -L${SRCDIR}/build/linux/arm64_static -L${SRCDIR}/build/linux/arm64_static/src -L${SRCDIR}/build/linux/arm64_static/ggml/src // #include From f6f759fc5fb4868125b8a25c28ce96d2c0980ef7 Mon Sep 17 00:00:00 2001 From: Daniel Hiltgen Date: Tue, 9 Jul 2024 10:27:53 -0700 Subject: [PATCH 055/384] Detect CUDA OS Overhead This adds logic to detect skew between the driver and management library which can be attributed to OS overhead and records that so we can adjust subsequent management library free VRAM updates and avoid OOM scenarios. --- gpu/gpu.go | 27 +++++++++++++++++++++++++++ gpu/types.go | 3 ++- 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/gpu/gpu.go b/gpu/gpu.go index 29a3c1037..58144991d 100644 --- a/gpu/gpu.go +++ b/gpu/gpu.go @@ -274,6 +274,28 @@ func GetGPUInfo() GpuInfoList { gpuInfo.DriverMajor = driverMajor gpuInfo.DriverMinor = driverMinor + // query the management library as well so we can record any skew between the two + // which represents overhead on the GPU we must set aside on subsequent updates + if cHandles.nvml != nil { + C.nvml_get_free(*cHandles.nvml, C.int(gpuInfo.index), &memInfo.free, &memInfo.total, &memInfo.used) + if memInfo.err != nil { + slog.Warn("error looking up nvidia GPU memory", "error", C.GoString(memInfo.err)) + C.free(unsafe.Pointer(memInfo.err)) + } else { + if memInfo.free != 0 && uint64(memInfo.free) > gpuInfo.FreeMemory { + gpuInfo.OSOverhead = uint64(memInfo.free) - gpuInfo.FreeMemory + slog.Info("detected OS VRAM overhead", + "id", gpuInfo.ID, + "library", gpuInfo.Library, + "compute", gpuInfo.Compute, + "driver", fmt.Sprintf("%d.%d", gpuInfo.DriverMajor, gpuInfo.DriverMinor), + "name", gpuInfo.Name, + "overhead", format.HumanBytes2(gpuInfo.OSOverhead), + ) + } + } + } + // TODO potentially sort on our own algorithm instead of what the underlying GPU library does... cudaGPUs = append(cudaGPUs, gpuInfo) } @@ -374,9 +396,14 @@ func GetGPUInfo() GpuInfoList { slog.Warn("error looking up nvidia GPU memory") continue } + if cHandles.nvml != nil && gpu.OSOverhead > 0 { + // When using the management library update based on recorded overhead + memInfo.free -= C.uint64_t(gpu.OSOverhead) + } slog.Debug("updating cuda memory data", "gpu", gpu.ID, "name", gpu.Name, + "overhead", format.HumanBytes2(gpu.OSOverhead), slog.Group( "before", "total", format.HumanBytes2(gpu.TotalMemory), diff --git a/gpu/types.go b/gpu/types.go index 2eaa9bae9..7a7749b8e 100644 --- a/gpu/types.go +++ b/gpu/types.go @@ -52,7 +52,8 @@ type CPUInfo struct { type CudaGPUInfo struct { GpuInfo - index int //nolint:unused,nolintlint + OSOverhead uint64 // Memory overhead between the driver library and management library + index int //nolint:unused,nolintlint } type CudaGPUInfoList []CudaGPUInfo From 0aff67877ed01adc00056742c9a88143eeabf0c5 Mon Sep 17 00:00:00 2001 From: royjhan <65097070+royjhan@users.noreply.github.com> Date: Tue, 9 Jul 2024 13:48:31 -0700 Subject: [PATCH 056/384] separate request tests (#5578) --- openai/openai_test.go | 194 +++++++++++++++++------------------------- 1 file changed, 78 insertions(+), 116 deletions(-) diff --git a/openai/openai_test.go b/openai/openai_test.go index 4d21382c6..39e8dc58c 100644 --- a/openai/openai_test.go +++ b/openai/openai_test.go @@ -3,7 +3,6 @@ package openai import ( "bytes" "encoding/json" - "fmt" "io" "net/http" "net/http/httptest" @@ -16,49 +15,33 @@ import ( "github.com/stretchr/testify/assert" ) -func TestMiddleware(t *testing.T) { +func TestMiddlewareRequests(t *testing.T) { type testCase struct { Name string Method string Path string - TestPath string Handler func() gin.HandlerFunc - Endpoint func(c *gin.Context) Setup func(t *testing.T, req *http.Request) - Expected func(t *testing.T, resp *httptest.ResponseRecorder) + Expected func(t *testing.T, req *http.Request) + } + + var capturedRequest *http.Request + + captureRequestMiddleware := func() gin.HandlerFunc { + return func(c *gin.Context) { + bodyBytes, _ := io.ReadAll(c.Request.Body) + c.Request.Body = io.NopCloser(bytes.NewReader(bodyBytes)) + capturedRequest = c.Request + c.Next() + } } testCases := []testCase{ { - Name: "chat handler", - Method: http.MethodPost, - Path: "/api/chat", - TestPath: "/api/chat", - Handler: ChatMiddleware, - Endpoint: func(c *gin.Context) { - var chatReq api.ChatRequest - if err := c.ShouldBindJSON(&chatReq); err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": "invalid request"}) - return - } - - userMessage := chatReq.Messages[0].Content - var assistantMessage string - - switch userMessage { - case "Hello": - assistantMessage = "Hello!" - default: - assistantMessage = "I'm not sure how to respond to that." - } - - c.JSON(http.StatusOK, api.ChatResponse{ - Message: api.Message{ - Role: "assistant", - Content: assistantMessage, - }, - }) - }, + Name: "chat handler", + Method: http.MethodPost, + Path: "/api/chat", + Handler: ChatMiddleware, Setup: func(t *testing.T, req *http.Request) { body := ChatCompletionRequest{ Model: "test-model", @@ -70,88 +53,26 @@ func TestMiddleware(t *testing.T) { req.Body = io.NopCloser(bytes.NewReader(bodyBytes)) req.Header.Set("Content-Type", "application/json") }, - Expected: func(t *testing.T, resp *httptest.ResponseRecorder) { - assert.Equal(t, http.StatusOK, resp.Code) - - var chatResp ChatCompletion - if err := json.NewDecoder(resp.Body).Decode(&chatResp); err != nil { + Expected: func(t *testing.T, req *http.Request) { + var chatReq api.ChatRequest + if err := json.NewDecoder(req.Body).Decode(&chatReq); err != nil { t.Fatal(err) } - if chatResp.Object != "chat.completion" { - t.Fatalf("expected chat.completion, got %s", chatResp.Object) + if chatReq.Messages[0].Role != "user" { + t.Fatalf("expected 'user', got %s", chatReq.Messages[0].Role) } - if chatResp.Choices[0].Message.Content != "Hello!" { - t.Fatalf("expected Hello!, got %s", chatResp.Choices[0].Message.Content) + if chatReq.Messages[0].Content != "Hello" { + t.Fatalf("expected 'Hello', got %s", chatReq.Messages[0].Content) } }, }, { - Name: "completions handler", - Method: http.MethodPost, - Path: "/api/generate", - TestPath: "/api/generate", - Handler: CompletionsMiddleware, - Endpoint: func(c *gin.Context) { - c.JSON(http.StatusOK, api.GenerateResponse{ - Response: "Hello!", - }) - }, - Setup: func(t *testing.T, req *http.Request) { - body := CompletionRequest{ - Model: "test-model", - Prompt: "Hello", - } - - bodyBytes, _ := json.Marshal(body) - - req.Body = io.NopCloser(bytes.NewReader(bodyBytes)) - req.Header.Set("Content-Type", "application/json") - }, - Expected: func(t *testing.T, resp *httptest.ResponseRecorder) { - assert.Equal(t, http.StatusOK, resp.Code) - var completionResp Completion - if err := json.NewDecoder(resp.Body).Decode(&completionResp); err != nil { - t.Fatal(err) - } - - if completionResp.Object != "text_completion" { - t.Fatalf("expected text_completion, got %s", completionResp.Object) - } - - if completionResp.Choices[0].Text != "Hello!" { - t.Fatalf("expected Hello!, got %s", completionResp.Choices[0].Text) - } - }, - }, - { - Name: "completions handler with params", - Method: http.MethodPost, - Path: "/api/generate", - TestPath: "/api/generate", - Handler: CompletionsMiddleware, - Endpoint: func(c *gin.Context) { - var generateReq api.GenerateRequest - if err := c.ShouldBindJSON(&generateReq); err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": "invalid request"}) - return - } - - temperature := generateReq.Options["temperature"].(float64) - var assistantMessage string - - switch temperature { - case 1.6: - assistantMessage = "Received temperature of 1.6" - default: - assistantMessage = fmt.Sprintf("Received temperature of %f", temperature) - } - - c.JSON(http.StatusOK, api.GenerateResponse{ - Response: assistantMessage, - }) - }, + Name: "completions handler", + Method: http.MethodPost, + Path: "/api/generate", + Handler: CompletionsMiddleware, Setup: func(t *testing.T, req *http.Request) { temp := float32(0.8) body := CompletionRequest{ @@ -165,24 +86,65 @@ func TestMiddleware(t *testing.T) { req.Body = io.NopCloser(bytes.NewReader(bodyBytes)) req.Header.Set("Content-Type", "application/json") }, - Expected: func(t *testing.T, resp *httptest.ResponseRecorder) { - assert.Equal(t, http.StatusOK, resp.Code) - var completionResp Completion - if err := json.NewDecoder(resp.Body).Decode(&completionResp); err != nil { + Expected: func(t *testing.T, req *http.Request) { + var genReq api.GenerateRequest + if err := json.NewDecoder(req.Body).Decode(&genReq); err != nil { t.Fatal(err) } - if completionResp.Object != "text_completion" { - t.Fatalf("expected text_completion, got %s", completionResp.Object) + if genReq.Prompt != "Hello" { + t.Fatalf("expected 'Hello', got %s", genReq.Prompt) } - if completionResp.Choices[0].Text != "Received temperature of 1.6" { - t.Fatalf("expected Received temperature of 1.6, got %s", completionResp.Choices[0].Text) + if genReq.Options["temperature"] != 1.6 { + t.Fatalf("expected 1.6, got %f", genReq.Options["temperature"]) } }, }, + } + + gin.SetMode(gin.TestMode) + router := gin.New() + + endpoint := func(c *gin.Context) { + c.Status(http.StatusOK) + } + + for _, tc := range testCases { + t.Run(tc.Name, func(t *testing.T) { + router = gin.New() + router.Use(captureRequestMiddleware()) + router.Use(tc.Handler()) + router.Handle(tc.Method, tc.Path, endpoint) + req, _ := http.NewRequest(tc.Method, tc.Path, nil) + + if tc.Setup != nil { + tc.Setup(t, req) + } + + resp := httptest.NewRecorder() + router.ServeHTTP(resp, req) + + tc.Expected(t, capturedRequest) + }) + } +} + +func TestMiddlewareResponses(t *testing.T) { + type testCase struct { + Name string + Method string + Path string + TestPath string + Handler func() gin.HandlerFunc + Endpoint func(c *gin.Context) + Setup func(t *testing.T, req *http.Request) + Expected func(t *testing.T, resp *httptest.ResponseRecorder) + } + + testCases := []testCase{ { - Name: "completions handler with error", + Name: "completions handler error forwarding", Method: http.MethodPost, Path: "/api/generate", TestPath: "/api/generate", From 4918fae535cb3d146100bacc0eff67a8579a8a7f Mon Sep 17 00:00:00 2001 From: royjhan <65097070+royjhan@users.noreply.github.com> Date: Tue, 9 Jul 2024 14:01:26 -0700 Subject: [PATCH 057/384] OpenAI v1/completions: allow stop token list (#5551) * stop token parsing fix * add stop test --- openai/openai.go | 14 +++++++++----- openai/openai_test.go | 11 +++++++++++ 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/openai/openai.go b/openai/openai.go index f1e75bf21..1707da14b 100644 --- a/openai/openai.go +++ b/openai/openai.go @@ -338,12 +338,16 @@ func fromCompleteRequest(r CompletionRequest) (api.GenerateRequest, error) { switch stop := r.Stop.(type) { case string: options["stop"] = []string{stop} - case []string: - options["stop"] = stop - default: - if r.Stop != nil { - return api.GenerateRequest{}, fmt.Errorf("invalid type for 'stop' field: %T", r.Stop) + case []any: + var stops []string + for _, s := range stop { + if str, ok := s.(string); ok { + stops = append(stops, str) + } else { + return api.GenerateRequest{}, fmt.Errorf("invalid type for 'stop' field: %T", s) + } } + options["stop"] = stops } if r.MaxTokens != nil { diff --git a/openai/openai_test.go b/openai/openai_test.go index 39e8dc58c..5f1ae52e9 100644 --- a/openai/openai_test.go +++ b/openai/openai_test.go @@ -79,6 +79,7 @@ func TestMiddlewareRequests(t *testing.T) { Model: "test-model", Prompt: "Hello", Temperature: &temp, + Stop: []string{"\n", "stop"}, } bodyBytes, _ := json.Marshal(body) @@ -99,6 +100,16 @@ func TestMiddlewareRequests(t *testing.T) { if genReq.Options["temperature"] != 1.6 { t.Fatalf("expected 1.6, got %f", genReq.Options["temperature"]) } + + stopTokens, ok := genReq.Options["stop"].([]any) + + if !ok { + t.Fatalf("expected stop tokens to be a list") + } + + if stopTokens[0] != "\n" || stopTokens[1] != "stop" { + t.Fatalf("expected ['\\n', 'stop'], got %v", stopTokens) + } }, }, } From f4408219e92a7b22107a68d0b3f5eb545c06aed9 Mon Sep 17 00:00:00 2001 From: Daniel Hiltgen Date: Fri, 5 Jul 2024 15:30:06 -0700 Subject: [PATCH 058/384] Refine scheduler unit tests for reliability This breaks up some of the test scenarios to create a more reliable set of tests, as well as adding a little more coverage. --- server/sched_test.go | 327 ++++++++++++++++++++++++++----------------- 1 file changed, 196 insertions(+), 131 deletions(-) diff --git a/server/sched_test.go b/server/sched_test.go index 3fbd188a7..c16b407d7 100644 --- a/server/sched_test.go +++ b/server/sched_test.go @@ -7,6 +7,7 @@ import ( "fmt" "log/slog" "os" + "runtime" "testing" "time" @@ -94,7 +95,7 @@ func TestLoad(t *testing.T) { require.Len(t, s.expiredCh, 1) } -type bundle struct { +type reqBundle struct { ctx context.Context //nolint:containedctx ctxDone func() srv *mockLlm @@ -102,13 +103,13 @@ type bundle struct { ggml *llm.GGML } -func (scenario *bundle) newServer(gpus gpu.GpuInfoList, model string, ggml *llm.GGML, adapters []string, projectors []string, opts api.Options, numParallel int) (llm.LlamaServer, error) { +func (scenario *reqBundle) newServer(gpus gpu.GpuInfoList, model string, ggml *llm.GGML, adapters []string, projectors []string, opts api.Options, numParallel int) (llm.LlamaServer, error) { return scenario.srv, nil } -func newScenario(t *testing.T, ctx context.Context, modelName string, estimatedVRAM uint64) *bundle { - scenario := &bundle{} - scenario.ctx, scenario.ctxDone = context.WithCancel(ctx) +func newScenarioRequest(t *testing.T, ctx context.Context, modelName string, estimatedVRAM uint64, duration *api.Duration) *reqBundle { + b := &reqBundle{} + b.ctx, b.ctxDone = context.WithCancel(ctx) t.Helper() f, err := os.CreateTemp(t.TempDir(), modelName) @@ -135,124 +136,154 @@ func newScenario(t *testing.T, ctx context.Context, modelName string, estimatedV fname := f.Name() model := &Model{Name: modelName, ModelPath: fname} - scenario.ggml, err = llm.LoadModel(model.ModelPath, 0) + b.ggml, err = llm.LoadModel(model.ModelPath, 0) require.NoError(t, err) - scenario.req = &LlmRequest{ - ctx: scenario.ctx, + if duration == nil { + duration = &api.Duration{Duration: 5 * time.Millisecond} + } + b.req = &LlmRequest{ + ctx: b.ctx, model: model, opts: api.DefaultOptions(), - sessionDuration: &api.Duration{Duration: 5 * time.Millisecond}, + sessionDuration: duration, successCh: make(chan *runnerRef, 1), errCh: make(chan error, 1), } - scenario.srv = &mockLlm{estimatedVRAM: estimatedVRAM, estimatedVRAMByGPU: map[string]uint64{"": estimatedVRAM}} - return scenario + b.srv = &mockLlm{estimatedVRAM: estimatedVRAM, estimatedVRAMByGPU: map[string]uint64{"": estimatedVRAM}} + return b } -func TestRequests(t *testing.T) { - ctx, done := context.WithTimeout(context.Background(), 10*time.Second) +func getGpuFn() gpu.GpuInfoList { + g := gpu.GpuInfo{Library: "metal"} + g.TotalMemory = 24 * format.GigaByte + g.FreeMemory = 12 * format.GigaByte + return []gpu.GpuInfo{g} +} + +func getCpuFn() gpu.GpuInfoList { + g := gpu.GpuInfo{Library: "cpu"} + g.TotalMemory = 32 * format.GigaByte + g.FreeMemory = 26 * format.GigaByte + return []gpu.GpuInfo{g} +} + +func TestRequestsSameModelSameRequest(t *testing.T) { + ctx, done := context.WithTimeout(context.Background(), 500*time.Millisecond) defer done() - - // Same model, same request - scenario1a := newScenario(t, ctx, "ollama-model-1", 10) - scenario1a.req.sessionDuration = &api.Duration{Duration: 5 * time.Millisecond} - scenario1b := newScenario(t, ctx, "ollama-model-1", 11) - scenario1b.req.model = scenario1a.req.model - scenario1b.ggml = scenario1a.ggml - scenario1b.req.sessionDuration = &api.Duration{Duration: 0} - - // simple reload of same model - scenario2a := newScenario(t, ctx, "ollama-model-1", 20) - tmpModel := *scenario1a.req.model - scenario2a.req.model = &tmpModel - scenario2a.ggml = scenario1a.ggml - scenario2a.req.sessionDuration = &api.Duration{Duration: 5 * time.Millisecond} - - // Multiple loaded models - scenario3a := newScenario(t, ctx, "ollama-model-3a", 1*format.GigaByte) - scenario3b := newScenario(t, ctx, "ollama-model-3b", 24*format.GigaByte) - scenario3c := newScenario(t, ctx, "ollama-model-4a", 30) - scenario3c.req.opts.NumGPU = 0 // CPU load, will be allowed - scenario3d := newScenario(t, ctx, "ollama-model-3c", 30) // Needs prior unloaded - s := InitScheduler(ctx) - s.getGpuFn = func() gpu.GpuInfoList { - g := gpu.GpuInfo{Library: "metal"} - g.TotalMemory = 24 * format.GigaByte - g.FreeMemory = 12 * format.GigaByte - return []gpu.GpuInfo{g} - } - s.getCpuFn = func() gpu.GpuInfoList { - g := gpu.GpuInfo{Library: "cpu"} - g.TotalMemory = 32 * format.GigaByte - g.FreeMemory = 26 * format.GigaByte - return []gpu.GpuInfo{g} - } - s.newServerFn = scenario1a.newServer - slog.Info("scenario1a") - s.pendingReqCh <- scenario1a.req + s.getGpuFn = getGpuFn + s.getCpuFn = getCpuFn + a := newScenarioRequest(t, ctx, "ollama-model-1", 10, &api.Duration{Duration: 5 * time.Millisecond}) + b := newScenarioRequest(t, ctx, "ollama-model-1", 11, &api.Duration{Duration: 0}) + b.req.model = a.req.model + b.ggml = a.ggml + + s.newServerFn = a.newServer + slog.Info("a") + s.pendingReqCh <- a.req require.Len(t, s.pendingReqCh, 1) s.Run(ctx) select { - case resp := <-scenario1a.req.successCh: - require.Equal(t, resp.llama, scenario1a.srv) + case resp := <-a.req.successCh: + require.Equal(t, resp.llama, a.srv) require.Empty(t, s.pendingReqCh) - require.Empty(t, scenario1a.req.errCh) - case err := <-scenario1a.req.errCh: + require.Empty(t, a.req.errCh) + case err := <-a.req.errCh: t.Fatal(err.Error()) case <-ctx.Done(): t.Fatal("timeout") } // Same runner as first request due to not needing a reload - s.newServerFn = scenario1b.newServer - slog.Info("scenario1b") - s.pendingReqCh <- scenario1b.req + s.newServerFn = b.newServer + slog.Info("b") + s.pendingReqCh <- b.req select { - case resp := <-scenario1b.req.successCh: - require.Equal(t, resp.llama, scenario1a.srv) + case resp := <-b.req.successCh: + require.Equal(t, resp.llama, a.srv) require.Empty(t, s.pendingReqCh) - require.Empty(t, scenario1b.req.errCh) - case err := <-scenario1b.req.errCh: + require.Empty(t, b.req.errCh) + case err := <-b.req.errCh: + t.Fatal(err.Error()) + case <-ctx.Done(): + t.Fatal("timeout") + } +} + +func TestRequestsSimpleReloadSameModel(t *testing.T) { + ctx, done := context.WithTimeout(context.Background(), 500*time.Millisecond) + defer done() + s := InitScheduler(ctx) + s.getGpuFn = getGpuFn + s.getCpuFn = getCpuFn + a := newScenarioRequest(t, ctx, "ollama-model-1", 10, &api.Duration{Duration: 5 * time.Millisecond}) + b := newScenarioRequest(t, ctx, "ollama-model-1", 20, &api.Duration{Duration: 5 * time.Millisecond}) + tmpModel := *a.req.model + b.req.model = &tmpModel + b.ggml = a.ggml + + s.newServerFn = a.newServer + slog.Info("a") + s.pendingReqCh <- a.req + require.Len(t, s.pendingReqCh, 1) + s.Run(ctx) + select { + case resp := <-a.req.successCh: + require.Equal(t, resp.llama, a.srv) + require.Empty(t, s.pendingReqCh) + require.Empty(t, a.req.errCh) + case err := <-a.req.errCh: t.Fatal(err.Error()) case <-ctx.Done(): t.Fatal("timeout") } // Trigger a reload - s.newServerFn = scenario2a.newServer - scenario2a.req.model.AdapterPaths = []string{"new"} - slog.Info("scenario2a") - s.pendingReqCh <- scenario2a.req + s.newServerFn = b.newServer + b.req.model.AdapterPaths = []string{"new"} + slog.Info("b") + s.pendingReqCh <- b.req // finish first two requests, so model can reload time.Sleep(1 * time.Millisecond) - scenario1a.ctxDone() - scenario1b.ctxDone() + a.ctxDone() select { - case resp := <-scenario2a.req.successCh: - require.Equal(t, resp.llama, scenario2a.srv) + case resp := <-b.req.successCh: + require.Equal(t, resp.llama, b.srv) require.Empty(t, s.pendingReqCh) - require.Empty(t, scenario2a.req.errCh) - case err := <-scenario2a.req.errCh: + require.Empty(t, b.req.errCh) + case err := <-b.req.errCh: t.Fatal(err.Error()) case <-ctx.Done(): t.Fatal("timeout") } +} + +func TestRequestsMultipleLoadedModels(t *testing.T) { + ctx, done := context.WithTimeout(context.Background(), 500*time.Millisecond) + defer done() + s := InitScheduler(ctx) + s.getGpuFn = getGpuFn + s.getCpuFn = getCpuFn + + // Multiple loaded models + a := newScenarioRequest(t, ctx, "ollama-model-3a", 1*format.GigaByte, nil) + b := newScenarioRequest(t, ctx, "ollama-model-3b", 24*format.GigaByte, nil) + c := newScenarioRequest(t, ctx, "ollama-model-4a", 30, nil) + c.req.opts.NumGPU = 0 // CPU load, will be allowed + d := newScenarioRequest(t, ctx, "ollama-model-3c", 30, nil) // Needs prior unloaded envconfig.MaxRunners = 1 - s.newServerFn = scenario3a.newServer - slog.Info("scenario3a") - s.pendingReqCh <- scenario3a.req - // finish prior request, so new model can load - time.Sleep(1 * time.Millisecond) - scenario2a.ctxDone() + s.newServerFn = a.newServer + slog.Info("a") + s.pendingReqCh <- a.req + s.Run(ctx) select { - case resp := <-scenario3a.req.successCh: - require.Equal(t, resp.llama, scenario3a.srv) + case resp := <-a.req.successCh: + require.Equal(t, resp.llama, a.srv) require.Empty(t, s.pendingReqCh) - require.Empty(t, scenario3a.req.errCh) - case err := <-scenario3a.req.errCh: + require.Empty(t, a.req.errCh) + case err := <-a.req.errCh: t.Fatal(err.Error()) case <-ctx.Done(): t.Fatal("timeout") @@ -262,15 +293,15 @@ func TestRequests(t *testing.T) { s.loadedMu.Unlock() envconfig.MaxRunners = 0 - s.newServerFn = scenario3b.newServer - slog.Info("scenario3b") - s.pendingReqCh <- scenario3b.req + s.newServerFn = b.newServer + slog.Info("b") + s.pendingReqCh <- b.req select { - case resp := <-scenario3b.req.successCh: - require.Equal(t, resp.llama, scenario3b.srv) + case resp := <-b.req.successCh: + require.Equal(t, resp.llama, b.srv) require.Empty(t, s.pendingReqCh) - require.Empty(t, scenario3b.req.errCh) - case err := <-scenario3b.req.errCh: + require.Empty(t, b.req.errCh) + case err := <-b.req.errCh: t.Fatal(err.Error()) case <-ctx.Done(): t.Fatal("timeout") @@ -280,15 +311,15 @@ func TestRequests(t *testing.T) { s.loadedMu.Unlock() // This is a CPU load with NumGPU = 0 so it should load - s.newServerFn = scenario3c.newServer - slog.Info("scenario3c") - s.pendingReqCh <- scenario3c.req + s.newServerFn = c.newServer + slog.Info("c") + s.pendingReqCh <- c.req select { - case resp := <-scenario3c.req.successCh: - require.Equal(t, resp.llama, scenario3c.srv) + case resp := <-c.req.successCh: + require.Equal(t, resp.llama, c.srv) require.Empty(t, s.pendingReqCh) - require.Empty(t, scenario3c.req.errCh) - case err := <-scenario3c.req.errCh: + require.Empty(t, c.req.errCh) + case err := <-c.req.errCh: t.Fatal(err.Error()) case <-ctx.Done(): t.Fatal("timeout") @@ -298,25 +329,25 @@ func TestRequests(t *testing.T) { s.loadedMu.Unlock() // Try to load a model that wont fit - s.newServerFn = scenario3d.newServer - slog.Info("scenario3d") + s.newServerFn = d.newServer + slog.Info("d") s.loadedMu.Lock() require.Len(t, s.loaded, 3) s.loadedMu.Unlock() - scenario3a.ctxDone() // Won't help since this one isn't big enough to make room + a.ctxDone() // Won't help since this one isn't big enough to make room time.Sleep(2 * time.Millisecond) - s.pendingReqCh <- scenario3d.req + s.pendingReqCh <- d.req // finish prior request, so new model can load time.Sleep(6 * time.Millisecond) s.loadedMu.Lock() require.Len(t, s.loaded, 2) s.loadedMu.Unlock() - scenario3b.ctxDone() + b.ctxDone() select { - case resp := <-scenario3d.req.successCh: - require.Equal(t, resp.llama, scenario3d.srv) + case resp := <-d.req.successCh: + require.Equal(t, resp.llama, d.srv) require.Empty(t, s.pendingReqCh) - require.Empty(t, scenario3d.req.errCh) + require.Empty(t, d.req.errCh) case <-ctx.Done(): t.Fatal("timeout") } @@ -325,30 +356,59 @@ func TestRequests(t *testing.T) { s.loadedMu.Unlock() } +func TestRequestsModelTooBigForSystem(t *testing.T) { + ctx, done := context.WithTimeout(context.Background(), 500*time.Millisecond) + defer done() + s := InitScheduler(ctx) + s.getGpuFn = func() gpu.GpuInfoList { + g := gpu.GpuInfo{Library: "metal"} + g.TotalMemory = 4 * format.MebiByte + g.FreeMemory = 3 * format.MebiByte + return []gpu.GpuInfo{g} + } + + s.getCpuFn = func() gpu.GpuInfoList { + g := gpu.GpuInfo{Library: "cpu"} + g.TotalMemory = 4 * format.MebiByte + g.FreeMemory = 2 * format.MebiByte + return []gpu.GpuInfo{g} + } + a := newScenarioRequest(t, ctx, "ollama-model-1", 10, &api.Duration{Duration: 5 * time.Millisecond}) + + s.newServerFn = a.newServer + slog.Info("a") + s.pendingReqCh <- a.req + require.Len(t, s.pendingReqCh, 1) + s.Run(ctx) + select { + case <-a.req.successCh: + if runtime.GOOS == "linux" { + t.Fatal("request should have been rejected with out of space") + } + // else - Darwin and Windows don't reject right now + case err := <-a.req.errCh: + require.Contains(t, err.Error(), "too large") + case <-ctx.Done(): + t.Fatal("timeout") + } +} func TestGetRunner(t *testing.T) { ctx, done := context.WithTimeout(context.Background(), 100*time.Millisecond) defer done() - scenario1a := newScenario(t, ctx, "ollama-model-1a", 10) - scenario1a.req.sessionDuration = &api.Duration{Duration: 0} - scenario1b := newScenario(t, ctx, "ollama-model-1b", 10) - scenario1b.req.sessionDuration = &api.Duration{Duration: 0} - scenario1c := newScenario(t, ctx, "ollama-model-1c", 10) - scenario1c.req.sessionDuration = &api.Duration{Duration: 0} + a := newScenarioRequest(t, ctx, "ollama-model-1a", 10, &api.Duration{Duration: 2 * time.Millisecond}) + b := newScenarioRequest(t, ctx, "ollama-model-1b", 10, &api.Duration{Duration: 2 * time.Millisecond}) + c := newScenarioRequest(t, ctx, "ollama-model-1c", 10, &api.Duration{Duration: 2 * time.Millisecond}) envconfig.MaxQueuedRequests = 1 s := InitScheduler(ctx) - s.getGpuFn = func() gpu.GpuInfoList { - g := gpu.GpuInfo{Library: "metal"} - g.TotalMemory = 24 * format.GigaByte - g.FreeMemory = 12 * format.GigaByte - return []gpu.GpuInfo{g} - } - s.newServerFn = scenario1a.newServer - slog.Info("scenario1a") - successCh1a, errCh1a := s.GetRunner(scenario1a.ctx, scenario1a.req.model, scenario1a.req.opts, scenario1a.req.sessionDuration) + s.getGpuFn = getGpuFn + s.getCpuFn = getCpuFn + s.newServerFn = a.newServer + slog.Info("a") + successCh1a, errCh1a := s.GetRunner(a.ctx, a.req.model, a.req.opts, a.req.sessionDuration) require.Len(t, s.pendingReqCh, 1) - slog.Info("scenario1b") - successCh1b, errCh1b := s.GetRunner(scenario1b.ctx, scenario1b.req.model, scenario1b.req.opts, scenario1b.req.sessionDuration) + slog.Info("b") + successCh1b, errCh1b := s.GetRunner(b.ctx, b.req.model, b.req.opts, b.req.sessionDuration) require.Len(t, s.pendingReqCh, 1) require.Empty(t, successCh1b) require.Len(t, errCh1b, 1) @@ -357,22 +417,24 @@ func TestGetRunner(t *testing.T) { s.Run(ctx) select { case resp := <-successCh1a: - require.Equal(t, resp.llama, scenario1a.srv) + require.Equal(t, resp.llama, a.srv) require.Empty(t, s.pendingReqCh) require.Empty(t, errCh1a) + case err := <-errCh1a: + t.Fatal(err.Error()) case <-ctx.Done(): t.Fatal("timeout") } - scenario1a.ctxDone() + a.ctxDone() // Set "a" model to idle so it can unload s.loadedMu.Lock() require.Len(t, s.loaded, 1) s.loadedMu.Unlock() - scenario1c.req.model.ModelPath = "bad path" - slog.Info("scenario1c") - successCh1c, errCh1c := s.GetRunner(scenario1c.ctx, scenario1c.req.model, scenario1c.req.opts, scenario1c.req.sessionDuration) + c.req.model.ModelPath = "bad path" + slog.Info("c") + successCh1c, errCh1c := s.GetRunner(c.ctx, c.req.model, c.req.opts, c.req.sessionDuration) // Starts in pending channel, then should be quickly processsed to return an error - time.Sleep(5 * time.Millisecond) + time.Sleep(20 * time.Millisecond) // Long enough for the "a" model to expire and unload require.Empty(t, successCh1c) s.loadedMu.Lock() require.Empty(t, s.loaded) @@ -380,7 +442,7 @@ func TestGetRunner(t *testing.T) { require.Len(t, errCh1c, 1) err = <-errCh1c require.Contains(t, err.Error(), "bad path") - scenario1b.ctxDone() + b.ctxDone() } // TODO - add one scenario that triggers the bogus finished event with positive ref count @@ -389,7 +451,7 @@ func TestPrematureExpired(t *testing.T) { defer done() // Same model, same request - scenario1a := newScenario(t, ctx, "ollama-model-1a", 10) + scenario1a := newScenarioRequest(t, ctx, "ollama-model-1a", 10, nil) s := InitScheduler(ctx) s.getGpuFn = func() gpu.GpuInfoList { g := gpu.GpuInfo{Library: "metal"} @@ -411,6 +473,8 @@ func TestPrematureExpired(t *testing.T) { s.loadedMu.Unlock() slog.Info("sending premature expired event now") s.expiredCh <- resp // Shouldn't happen in real life, but make sure its safe + case err := <-errCh1a: + t.Fatal(err.Error()) case <-ctx.Done(): t.Fatal("timeout") } @@ -446,6 +510,8 @@ func TestUseLoadedRunner(t *testing.T) { select { case success := <-req.successCh: require.Equal(t, r1, success) + case err := <-req.errCh: + t.Fatal(err.Error()) case <-ctx.Done(): t.Fatal("timeout") } @@ -625,8 +691,7 @@ func TestAlreadyCanceled(t *testing.T) { defer done() dctx, done2 := context.WithCancel(ctx) done2() - scenario1a := newScenario(t, dctx, "ollama-model-1", 10) - scenario1a.req.sessionDuration = &api.Duration{Duration: 0} + scenario1a := newScenarioRequest(t, dctx, "ollama-model-1", 10, &api.Duration{Duration: 0}) s := InitScheduler(ctx) slog.Info("scenario1a") s.pendingReqCh <- scenario1a.req From 73e2c8f68fe075ea159a20bbf778c0cf801316ad Mon Sep 17 00:00:00 2001 From: Daniel Hiltgen Date: Tue, 9 Jul 2024 15:28:25 -0700 Subject: [PATCH 059/384] Fix context exhaustion integration test for small gpus On the smaller GPUs, the initial model load of llama2 took over 30s (the default timeout for the DoGenerate helper) --- integration/context_test.go | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/integration/context_test.go b/integration/context_test.go index 46fac5ead..f1342e16c 100644 --- a/integration/context_test.go +++ b/integration/context_test.go @@ -12,7 +12,7 @@ import ( func TestContextExhaustion(t *testing.T) { // Longer needed for small footprint GPUs - ctx, cancel := context.WithTimeout(context.Background(), 6*time.Minute) + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute) defer cancel() // Set up the test data req := api.GenerateRequest{ @@ -25,5 +25,10 @@ func TestContextExhaustion(t *testing.T) { "num_ctx": 128, }, } - GenerateTestHelper(ctx, t, req, []string{"once", "upon", "lived"}) + client, _, cleanup := InitServerConnection(ctx, t) + defer cleanup() + if err := PullIfMissing(ctx, client, req.Model); err != nil { + t.Fatalf("PullIfMissing failed: %v", err) + } + DoGenerate(ctx, t, client, req, []string{"once", "upon", "lived"}, 120*time.Second, 10*time.Second) } From 22c81f62ec845bd8f77215ae5599be14117ec8db Mon Sep 17 00:00:00 2001 From: Daniel Hiltgen Date: Wed, 10 Jul 2024 09:01:33 -0700 Subject: [PATCH 060/384] Remove duplicate merge glitch --- llm/server.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/llm/server.go b/llm/server.go index 08dc04d5a..aa504d193 100644 --- a/llm/server.go +++ b/llm/server.go @@ -254,10 +254,6 @@ func NewLlamaServer(gpus gpu.GpuInfoList, model string, ggml *GGML, adapters, pr params = append(params, "--tensor-split", estimate.TensorSplit) } - if estimate.TensorSplit != "" { - params = append(params, "--tensor-split", estimate.TensorSplit) - } - for i := range len(servers) { dir := availableServers[servers[i]] if dir == "" { From 1f50356e8e3c3a2956c5ffacc3b9fa33b8285541 Mon Sep 17 00:00:00 2001 From: Daniel Hiltgen Date: Wed, 10 Jul 2024 11:01:22 -0700 Subject: [PATCH 061/384] Bump ROCm on windows to 6.1.2 This also adjusts our algorithm to favor our bundled ROCm. I've confirmed VRAM reporting still doesn't work properly so we can't yet enable concurrency by default. --- .github/workflows/release.yaml | 2 +- .github/workflows/test.yaml | 2 +- docs/faq.md | 2 +- gpu/amd_common.go | 23 +++++++++++------------ gpu/amd_windows.go | 4 ++-- llm/generate/gen_windows.ps1 | 12 +----------- 6 files changed, 17 insertions(+), 28 deletions(-) diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 61ca3c433..5ae630c31 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -147,7 +147,7 @@ jobs: run: | $ErrorActionPreference = "Stop" write-host "downloading AMD HIP Installer" - Invoke-WebRequest -Uri "https://download.amd.com/developer/eula/rocm-hub/AMD-Software-PRO-Edition-23.Q4-WinSvr2022-For-HIP.exe" -OutFile "${env:RUNNER_TEMP}\rocm-install.exe" + Invoke-WebRequest -Uri "https://download.amd.com/developer/eula/rocm-hub/AMD-Software-PRO-Edition-24.Q3-WinSvr2022-For-HIP.exe" -OutFile "${env:RUNNER_TEMP}\rocm-install.exe" write-host "Installing AMD HIP" Start-Process "${env:RUNNER_TEMP}\rocm-install.exe" -ArgumentList '-install' -NoNewWindow -Wait write-host "Completed AMD HIP" diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 13d1c957c..977d8da14 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -169,7 +169,7 @@ jobs: run: | $ErrorActionPreference = "Stop" write-host "downloading AMD HIP Installer" - Invoke-WebRequest -Uri "https://download.amd.com/developer/eula/rocm-hub/AMD-Software-PRO-Edition-23.Q4-WinSvr2022-For-HIP.exe" -OutFile "${env:RUNNER_TEMP}\rocm-install.exe" + Invoke-WebRequest -Uri "https://download.amd.com/developer/eula/rocm-hub/AMD-Software-PRO-Edition-24.Q3-WinSvr2022-For-HIP.exe" -OutFile "${env:RUNNER_TEMP}\rocm-install.exe" write-host "Installing AMD HIP" Start-Process "${env:RUNNER_TEMP}\rocm-install.exe" -ArgumentList '-install' -NoNewWindow -Wait write-host "Completed AMD HIP" diff --git a/docs/faq.md b/docs/faq.md index 574112461..da1848f7f 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -272,4 +272,4 @@ The following server settings may be used to adjust how Ollama handles concurren - `OLLAMA_NUM_PARALLEL` - The maximum number of parallel requests each model will process at the same time. The default will auto-select either 4 or 1 based on available memory. - `OLLAMA_MAX_QUEUE` - The maximum number of requests Ollama will queue when busy before rejecting additional requests. The default is 512 -Note: Windows with Radeon GPUs currently default to 1 model maximum due to limitations in ROCm v5.7 for available VRAM reporting. Once ROCm v6 is available, Windows Radeon will follow the defaults above. You may enable concurrent model loads on Radeon on Windows, but ensure you don't load more models than will fit into your GPUs VRAM. \ No newline at end of file +Note: Windows with Radeon GPUs currently default to 1 model maximum due to limitations in ROCm v5.7 for available VRAM reporting. Once ROCm v6.2 is available, Windows Radeon will follow the defaults above. You may enable concurrent model loads on Radeon on Windows, but ensure you don't load more models than will fit into your GPUs VRAM. \ No newline at end of file diff --git a/gpu/amd_common.go b/gpu/amd_common.go index 27a81e3f8..7d1cab7c1 100644 --- a/gpu/amd_common.go +++ b/gpu/amd_common.go @@ -49,9 +49,17 @@ func rocmGetVisibleDevicesEnv(gpuInfo []GpuInfo) (string, string) { } func commonAMDValidateLibDir() (string, error) { - // We try to favor system paths first, so that we can wire up the subprocess to use - // the system version. Only use our bundled version if the system version doesn't work - // This gives users a more recovery options if versions have subtle problems at runtime + // Favor our bundled version + + // Installer payload location if we're running the installed binary + exe, err := os.Executable() + if err == nil { + rocmTargetDir := filepath.Join(filepath.Dir(exe), "rocm") + if rocmLibUsable(rocmTargetDir) { + slog.Debug("detected ROCM next to ollama executable " + rocmTargetDir) + return rocmTargetDir, nil + } + } // Prefer explicit HIP env var hipPath := os.Getenv("HIP_PATH") @@ -87,14 +95,5 @@ func commonAMDValidateLibDir() (string, error) { } } - // Installer payload location if we're running the installed binary - exe, err := os.Executable() - if err == nil { - rocmTargetDir := filepath.Join(filepath.Dir(exe), "rocm") - if rocmLibUsable(rocmTargetDir) { - slog.Debug("detected ROCM next to ollama executable " + rocmTargetDir) - return rocmTargetDir, nil - } - } return "", fmt.Errorf("no suitable rocm found, falling back to CPU") } diff --git a/gpu/amd_windows.go b/gpu/amd_windows.go index 8b6fabebb..5d09be8bd 100644 --- a/gpu/amd_windows.go +++ b/gpu/amd_windows.go @@ -22,8 +22,8 @@ const ( var ( // Used to validate if the given ROCm lib is usable - ROCmLibGlobs = []string{"hipblas.dll", "rocblas"} // TODO - probably include more coverage of files here... - RocmStandardLocations = []string{"C:\\Program Files\\AMD\\ROCm\\5.7\\bin"} // TODO glob? + ROCmLibGlobs = []string{"hipblas.dll", "rocblas"} // This is not sufficient to discern v5 vs v6 + RocmStandardLocations = []string{"C:\\Program Files\\AMD\\ROCm\\6.1\\bin"} // TODO glob? ) func AMDGetGPUInfo() []RocmGPUInfo { diff --git a/llm/generate/gen_windows.ps1 b/llm/generate/gen_windows.ps1 index 26bc4fa3e..beb964f98 100644 --- a/llm/generate/gen_windows.ps1 +++ b/llm/generate/gen_windows.ps1 @@ -6,18 +6,9 @@ function amdGPUs { if ($env:AMDGPU_TARGETS) { return $env:AMDGPU_TARGETS } - # TODO - load from some common data file for linux + windows build consistency + # Current supported rocblas list from ROCm v6.1.2 on windows $GPU_LIST = @( - "gfx900" "gfx906:xnack-" - "gfx908:xnack-" - "gfx90a:xnack+" - "gfx90a:xnack-" - "gfx940" - "gfx941" - "gfx942" - "gfx1010" - "gfx1012" "gfx1030" "gfx1100" "gfx1101" @@ -395,7 +386,6 @@ function build_rocm() { sign install - # Assumes v5.7, may need adjustments for v6 rm -ea 0 -recurse -force -path "${script:SRC_DIR}\dist\windows-${script:ARCH}\rocm\" md "${script:SRC_DIR}\dist\windows-${script:ARCH}\rocm\rocblas\library\" -ea 0 > $null cp "${env:HIP_PATH}\bin\hipblas.dll" "${script:SRC_DIR}\dist\windows-${script:ARCH}\rocm\" From 4e262eb2a8aaee31e228febc216c2a83a9a7e4d8 Mon Sep 17 00:00:00 2001 From: Jeffrey Morgan Date: Wed, 10 Jul 2024 13:17:13 -0700 Subject: [PATCH 062/384] remove `GGML_CUDA_FORCE_MMQ=on` from build (#5588) --- llm/generate/gen_linux.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/llm/generate/gen_linux.sh b/llm/generate/gen_linux.sh index 304eadbd9..5589f1ead 100755 --- a/llm/generate/gen_linux.sh +++ b/llm/generate/gen_linux.sh @@ -178,7 +178,7 @@ if [ -z "${OLLAMA_SKIP_CUDA_GENERATE}" -a -d "${CUDA_LIB_DIR}" ]; then CMAKE_CUDA_DEFS="-DGGML_CUDA=on -DCMAKE_CUDA_ARCHITECTURES=${CMAKE_CUDA_ARCHITECTURES} ${OLLAMA_CUSTOM_CUDA_DEFS}" echo "Building custom CUDA GPU" else - CMAKE_CUDA_DEFS="-DGGML_CUDA=on -DCMAKE_CUDA_FLAGS=-t8 -DGGML_CUDA_FORCE_MMQ=on -DCMAKE_CUDA_ARCHITECTURES=${CMAKE_CUDA_ARCHITECTURES} -DCMAKE_LIBRARY_PATH=/usr/local/cuda/compat" + CMAKE_CUDA_DEFS="-DGGML_CUDA=on -DCMAKE_CUDA_FLAGS=-t8 -DCMAKE_CUDA_ARCHITECTURES=${CMAKE_CUDA_ARCHITECTURES} -DCMAKE_LIBRARY_PATH=/usr/local/cuda/compat" fi CMAKE_DEFS="${COMMON_CMAKE_DEFS} ${CMAKE_DEFS} ${ARM64_DEFS} ${CMAKE_CUDA_DEFS}" BUILD_DIR="../build/linux/${ARCH}/cuda${CUDA_VARIANT}" From 5a739ff4cb27f7804903adfb674f8a1e197ea86f Mon Sep 17 00:00:00 2001 From: Michael Yang Date: Wed, 10 Jul 2024 13:18:04 -0700 Subject: [PATCH 063/384] chatglm graph --- llm/ggml.go | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/llm/ggml.go b/llm/ggml.go index cfead450d..fddb50391 100644 --- a/llm/ggml.go +++ b/llm/ggml.go @@ -424,6 +424,32 @@ func (llm GGML) GraphSize(context, batch uint64) (partialOffload, fullOffload ui 4*batch*(3*embedding+vocab)+embedding*vocab*105/128, 4*batch*(2*embedding+1+2*embeddingHeadsK*headsKV+context+context*headsKV)+4*embeddingHeadsK*context*headsKV+embedding*embeddingHeadsK*headsKV*9/16, ) + case "chatglm": + fullOffload = 4 * batch * (embedding + vocab) + partialOffload = 4*batch*(embedding+vocab) + embedding*vocab*105/128 + if qkvBias, ok := layers["blk.0"]["attn_qkv.bias"]; ok { + fullOffload = max( + fullOffload, + 4*batch*(2+ + 2*embedding+ + context+ + context*heads+ + embeddingHeadsK*heads+ + qkvBias.Shape[0]), + ) + + partialOffload = max( + partialOffload, + 4*batch*(1+ + 2*embedding+ + embeddingHeadsK*heads+ + context+ + context*heads)+ + 4*embeddingHeadsK*context+ + 4*context*embeddingHeadsK+ + 4*qkvBias.Shape[0], + ) + } } return From 41be28096aa597ded1ef91774ba3e6dfc0a8ccbb Mon Sep 17 00:00:00 2001 From: Michael Yang Date: Wed, 10 Jul 2024 11:00:07 -0700 Subject: [PATCH 064/384] add system prompt to first legacy template --- server/prompt_test.go | 2 +- server/routes_create_test.go | 4 +- template/template.go | 101 +++++++++++++++++++++++++++++++---- template/template_test.go | 61 ++++++++++++++++----- 4 files changed, 140 insertions(+), 28 deletions(-) diff --git a/server/prompt_test.go b/server/prompt_test.go index d4cee98c2..1435b143a 100644 --- a/server/prompt_test.go +++ b/server/prompt_test.go @@ -161,7 +161,7 @@ func TestChatPrompt(t *testing.T) { {Role: "user", Content: "A test. And a thumping good one at that, I'd wager."}, }, expect: expect{ - prompt: "You're a test, Harry! I-I'm a what? You are the Test Who Lived. A test. And a thumping good one at that, I'd wager. ", + prompt: "You are the Test Who Lived. You're a test, Harry! I-I'm a what? A test. And a thumping good one at that, I'd wager. ", }, }, } diff --git a/server/routes_create_test.go b/server/routes_create_test.go index 269a0ba12..40477937b 100644 --- a/server/routes_create_test.go +++ b/server/routes_create_test.go @@ -546,8 +546,8 @@ func TestCreateDetectTemplate(t *testing.T) { checkFileExists(t, filepath.Join(p, "blobs", "*"), []string{ filepath.Join(p, "blobs", "sha256-553c4a3f747b3d22a4946875f1cc8ed011c2930d83f864a0c7265f9ec0a20413"), - filepath.Join(p, "blobs", "sha256-9512c372dfc7d84d6065b8dd2b601aeed8cc1a78e7a7aa784a42fff37f5524b7"), - filepath.Join(p, "blobs", "sha256-b8b78cb8c6eefd14c06f1af042e6161255bf87bbf2dd14fce57cdac893db8139"), + filepath.Join(p, "blobs", "sha256-68b0323b2f21572bc09ba07554b16b379a5713ee48ef8c25a7661a1f71cfce77"), + filepath.Join(p, "blobs", "sha256-eb72fb7c550ee1f1dec4039bd65382acecf5f7536a30fb7ccace39a8d0cb590b"), }) }) diff --git a/template/template.go b/template/template.go index b133b97e9..0b8f24348 100644 --- a/template/template.go +++ b/template/template.go @@ -143,11 +143,14 @@ func (t *Template) Vars() []string { type Values struct { Messages []api.Message + + // forceLegacy is a flag used to test compatibility with legacy templates + forceLegacy bool } func (t *Template) Execute(w io.Writer, v Values) error { system, collated := collate(v.Messages) - if slices.Contains(t.Vars(), "messages") { + if !v.forceLegacy && slices.Contains(t.Vars(), "messages") { return t.Template.Execute(w, map[string]any{ "System": system, "Messages": collated, @@ -157,15 +160,19 @@ func (t *Template) Execute(w io.Writer, v Values) error { var b bytes.Buffer var prompt, response string for i, m := range collated { - if m.Role == "user" { + switch m.Role { + case "user": prompt = m.Content - } else { + if i != 0 { + system = "" + } + case "assistant": response = m.Content } if i != len(collated)-1 && prompt != "" && response != "" { if err := t.Template.Execute(&b, map[string]any{ - "System": "", + "System": system, "Prompt": prompt, "Response": response, }); err != nil { @@ -178,18 +185,21 @@ func (t *Template) Execute(w io.Writer, v Values) error { } var cut bool - tree := t.Template.Copy() - // for the last message, cut everything after "{{ .Response }}" - tree.Root.Nodes = slices.DeleteFunc(tree.Root.Nodes, func(n parse.Node) bool { - if slices.Contains(parseNode(n), "Response") { - cut = true + nodes := deleteNode(t.Template.Root.Copy(), func(n parse.Node) bool { + switch t := n.(type) { + case *parse.ActionNode: + case *parse.FieldNode: + if slices.Contains(t.Ident, "Response") { + cut = true + } } return cut }) - if err := template.Must(template.New("").AddParseTree("", tree)).Execute(&b, map[string]any{ - "System": system, + tree := parse.Tree{Root: nodes.(*parse.ListNode)} + if err := template.Must(template.New("").AddParseTree("", &tree)).Execute(&b, map[string]any{ + "System": "", "Prompt": prompt, }); err != nil { return err @@ -286,3 +296,72 @@ func parseNode(n parse.Node) []string { return nil } + +// deleteNode walks the node list and deletes nodes that match the predicate +// this is currently to remove the {{ .Response }} node from templates +func deleteNode(n parse.Node, fn func(parse.Node) bool) parse.Node { + var walk func(n parse.Node) parse.Node + walk = func(n parse.Node) parse.Node { + if fn(n) { + return nil + } + + switch t := n.(type) { + case *parse.ListNode: + var nodes []parse.Node + for _, c := range t.Nodes { + if n := walk(c); n != nil { + nodes = append(nodes, n) + } + } + + t.Nodes = nodes + return t + case *parse.IfNode: + t.BranchNode = *(walk(&t.BranchNode).(*parse.BranchNode)) + case *parse.WithNode: + t.BranchNode = *(walk(&t.BranchNode).(*parse.BranchNode)) + case *parse.RangeNode: + t.BranchNode = *(walk(&t.BranchNode).(*parse.BranchNode)) + case *parse.BranchNode: + t.List = walk(t.List).(*parse.ListNode) + if t.ElseList != nil { + t.ElseList = walk(t.ElseList).(*parse.ListNode) + } + case *parse.ActionNode: + n := walk(t.Pipe) + if n == nil { + return nil + } + + t.Pipe = n.(*parse.PipeNode) + case *parse.PipeNode: + var commands []*parse.CommandNode + for _, c := range t.Cmds { + var args []parse.Node + for _, a := range c.Args { + if n := walk(a); n != nil { + args = append(args, n) + } + } + + if len(args) == 0 { + return nil + } + + c.Args = args + commands = append(commands, c) + } + + if len(commands) == 0 { + return nil + } + + t.Cmds = commands + } + + return n + } + + return walk(n) +} diff --git a/template/template_test.go b/template/template_test.go index 428cdc77c..e702a1862 100644 --- a/template/template_test.go +++ b/template/template_test.go @@ -105,8 +105,8 @@ func TestTemplate(t *testing.T) { } for n, tt := range cases { + var actual bytes.Buffer t.Run(n, func(t *testing.T) { - var actual bytes.Buffer if err := tmpl.Execute(&actual, Values{Messages: tt}); err != nil { t.Fatal(err) } @@ -120,6 +120,25 @@ func TestTemplate(t *testing.T) { t.Errorf("mismatch (-got +want):\n%s", diff) } }) + + t.Run("legacy", func(t *testing.T) { + var legacy bytes.Buffer + if err := tmpl.Execute(&legacy, Values{Messages: tt, forceLegacy: true}); err != nil { + t.Fatal(err) + } + + legacyBytes := legacy.Bytes() + if slices.Contains([]string{"chatqa.gotmpl", "openchat.gotmpl", "vicuna.gotmpl"}, match) && legacyBytes[len(legacyBytes)-1] == ' ' { + t.Log("removing trailing space from legacy output") + legacyBytes = legacyBytes[:len(legacyBytes)-1] + } else if slices.Contains([]string{"codellama-70b-instruct.gotmpl", "llama2-chat.gotmpl", "mistral-instruct.gotmpl"}, match) { + t.Skip("legacy outputs cannot be compared to messages outputs") + } + + if diff := cmp.Diff(legacyBytes, actual.Bytes()); diff != "" { + t.Errorf("mismatch (-got +want):\n%s", diff) + } + }) } }) } @@ -136,6 +155,21 @@ func TestParse(t *testing.T) { {"{{ with .Tools }}{{ . }}{{ end }} {{ .System }} {{ .Prompt }}", []string{"prompt", "response", "system", "tools"}}, {"{{ range .Messages }}{{ .Role }} {{ .Content }}{{ end }}", []string{"content", "messages", "role"}}, {"{{ range .Messages }}{{ if eq .Role \"system\" }}SYSTEM: {{ .Content }}{{ else if eq .Role \"user\" }}USER: {{ .Content }}{{ else if eq .Role \"assistant\" }}ASSISTANT: {{ .Content }}{{ end }}{{ end }}", []string{"content", "messages", "role"}}, + {`{{- if .Messages }} +{{- if .System }}<|im_start|>system +{{ .System }}<|im_end|> +{{ end }} +{{- range .Messages }}<|im_start|>{{ .Role }} +{{ .Content }}<|im_end|> +{{ end }}<|im_start|>assistant +{{ else -}} +{{ if .System }}<|im_start|>system +{{ .System }}<|im_end|> +{{ end }}{{ if .Prompt }}<|im_start|>user +{{ .Prompt }}<|im_end|> +{{ end }}<|im_start|>assistant +{{ .Response }}<|im_end|> +{{- end -}}`, []string{"content", "messages", "prompt", "response", "role", "system"}}, } for _, tt := range cases { @@ -145,9 +179,8 @@ func TestParse(t *testing.T) { t.Fatal(err) } - vars := tmpl.Vars() - if !slices.Equal(tt.vars, vars) { - t.Errorf("expected %v, got %v", tt.vars, vars) + if diff := cmp.Diff(tmpl.Vars(), tt.vars); diff != "" { + t.Errorf("mismatch (-got +want):\n%s", diff) } }) } @@ -170,7 +203,7 @@ func TestExecuteWithMessages(t *testing.T) { {"no response", `[INST] {{ if .System }}{{ .System }}{{ "\n\n" }}{{ end }}{{ .Prompt }}[/INST] `}, {"response", `[INST] {{ if .System }}{{ .System }}{{ "\n\n" }}{{ end }}{{ .Prompt }}[/INST] {{ .Response }}`}, {"messages", `{{- range $index, $_ := .Messages }} -{{- if eq .Role "user" }}[INST] {{ if and (eq (len (slice $.Messages $index)) 1) $.System }}{{ $.System }}{{ "\n\n" }} +{{- if eq .Role "user" }}[INST] {{ if and (eq $index 0) $.System }}{{ $.System }}{{ "\n\n" }} {{- end }}{{ .Content }}[/INST] {{ else if eq .Role "assistant" }}{{ .Content }} {{- end }} {{- end }}`}, @@ -191,7 +224,7 @@ func TestExecuteWithMessages(t *testing.T) { {"response", `[INST] {{ if .System }}{{ .System }}{{ "\n\n" }}{{ end }}{{ .Prompt }}[/INST] {{ .Response }}`}, {"messages", ` {{- range $index, $_ := .Messages }} -{{- if eq .Role "user" }}[INST] {{ if and (eq (len (slice $.Messages $index)) 1) $.System }}{{ $.System }}{{ "\n\n" }} +{{- if eq .Role "user" }}[INST] {{ if and (eq $index 0) $.System }}{{ $.System }}{{ "\n\n" }} {{- end }}{{ .Content }}[/INST] {{ else if eq .Role "assistant" }}{{ .Content }} {{- end }} {{- end }}`}, @@ -204,9 +237,9 @@ func TestExecuteWithMessages(t *testing.T) { {Role: "user", Content: "What is your name?"}, }, }, - `[INST] Hello friend![/INST] Hello human![INST] You are a helpful assistant! + `[INST] You are a helpful assistant! -What is your name?[/INST] `, +Hello friend![/INST] Hello human![INST] What is your name?[/INST] `, }, { "chatml", @@ -221,7 +254,7 @@ What is your name?[/INST] `, `}, {"messages", ` {{- range $index, $_ := .Messages }} -{{- if and (eq .Role "user") (eq (len (slice $.Messages $index)) 1) $.System }}<|im_start|>system +{{- if and (eq .Role "user") (eq $index 0) $.System }}<|im_start|>system {{ $.System }}<|im_end|>{{ "\n" }} {{- end }}<|im_start|>{{ .Role }} {{ .Content }}<|im_end|>{{ "\n" }} @@ -236,12 +269,12 @@ What is your name?[/INST] `, {Role: "user", Content: "What is your name?"}, }, }, - `<|im_start|>user + `<|im_start|>system +You are a helpful assistant!<|im_end|> +<|im_start|>user Hello friend!<|im_end|> <|im_start|>assistant Hello human!<|im_end|> -<|im_start|>system -You are a helpful assistant!<|im_end|> <|im_start|>user What is your name?<|im_end|> <|im_start|>assistant @@ -300,8 +333,8 @@ Answer: `, t.Fatal(err) } - if b.String() != tt.expected { - t.Errorf("expected\n%s,\ngot\n%s", tt.expected, b.String()) + if diff := cmp.Diff(b.String(), tt.expected); diff != "" { + t.Errorf("mismatch (-got +want):\n%s", diff) } }) } From 19753c18c01183b4c974e36e89b0c7cbdcc3c38a Mon Sep 17 00:00:00 2001 From: Michael Yang Date: Wed, 10 Jul 2024 11:00:29 -0700 Subject: [PATCH 065/384] update embedded templates --- template/alfred.gotmpl | 4 ++-- template/alpaca.gotmpl | 8 +++++--- template/chatml.gotmpl | 4 ++-- template/chatqa.gotmpl | 7 ++++--- template/codellama-70b-instruct.gotmpl | 10 +++++----- template/falcon-instruct.gotmpl | 12 +++++++----- template/gemma-instruct.gotmpl | 7 ++++--- template/granite-instruct.gotmpl | 8 ++++---- template/llama2-chat.gotmpl | 8 ++++---- template/llama3-instruct.gotmpl | 4 ++-- template/magicoder.gotmpl | 5 +++-- template/mistral-instruct.gotmpl | 5 +++-- template/openchat.gotmpl | 12 ++++++------ template/phi-3.gotmpl | 4 ++-- template/solar-instruct.gotmpl | 7 ++++--- template/starcoder2-instruct.gotmpl | 5 ++--- .../alpaca.gotmpl/system-user-assistant-user | 4 +++- .../system-user-assistant-user | 1 + template/testdata/codellama-70b-instruct.gotmpl/user | 1 + .../user-assistant-user | 1 + .../openchat.gotmpl/system-user-assistant-user | 2 +- template/testdata/openchat.gotmpl/user | 2 +- .../testdata/openchat.gotmpl/user-assistant-user | 2 +- template/vicuna.gotmpl | 7 ++++--- template/zephyr.gotmpl | 4 ++-- 25 files changed, 74 insertions(+), 60 deletions(-) diff --git a/template/alfred.gotmpl b/template/alfred.gotmpl index 44284f04c..71bc6706f 100644 --- a/template/alfred.gotmpl +++ b/template/alfred.gotmpl @@ -3,6 +3,6 @@ {{- end }} {{- range .Messages }}{{ .Content }} {{- end }} -{{- else }} +{{- else -}} {{ if .System }}{{ .System }}{{ end }}{{ if .Prompt }}{{ .Prompt }}{{ end }}{{ .Response }} -{{- end }} \ No newline at end of file +{{- end -}} \ No newline at end of file diff --git a/template/alpaca.gotmpl b/template/alpaca.gotmpl index c1f69dc92..e9becb3d4 100644 --- a/template/alpaca.gotmpl +++ b/template/alpaca.gotmpl @@ -1,6 +1,7 @@ {{- if .Messages }} {{- if .System }}{{ .System }} -{{- end }} + +{{ end }} {{- range .Messages }} {{- if eq .Role "user" }}### Instruction: {{- else if eq .Role "assistant" }}### Response: @@ -8,7 +9,7 @@ {{ .Content }} {{ end }}### Response: -{{ else }} +{{ else -}} {{ if .System }}{{ .System }} {{ end }}{{ if .Prompt }}### Instruction: @@ -16,4 +17,5 @@ {{ end }}### Response: {{ .Response }} -{{- end }} \ No newline at end of file + +{{ end -}} \ No newline at end of file diff --git a/template/chatml.gotmpl b/template/chatml.gotmpl index d945547c7..eb8ab0dcd 100644 --- a/template/chatml.gotmpl +++ b/template/chatml.gotmpl @@ -5,11 +5,11 @@ {{- range .Messages }}<|im_start|>{{ .Role }} {{ .Content }}<|im_end|> {{ end }}<|im_start|>assistant -{{ else }} +{{ else -}} {{ if .System }}<|im_start|>system {{ .System }}<|im_end|> {{ end }}{{ if .Prompt }}<|im_start|>user {{ .Prompt }}<|im_end|> {{ end }}<|im_start|>assistant {{ .Response }}<|im_end|> -{{- end }} \ No newline at end of file +{{ end -}} \ No newline at end of file diff --git a/template/chatqa.gotmpl b/template/chatqa.gotmpl index 7022c4790..41c6ced59 100644 --- a/template/chatqa.gotmpl +++ b/template/chatqa.gotmpl @@ -8,10 +8,11 @@ {{- end }} {{ .Content }} {{ end }}Assistant: -{{- else }} +{{- else -}} {{ if .System }}System: {{ .System }} {{ end }}{{ if .Prompt }}User: {{ .Prompt }} -{{ end }}Assistant: <|begin_of_text|>{{ .Response }} -{{- end }} \ No newline at end of file +{{ end }}Assistant: {{ .Response }} + +{{ end -}} \ No newline at end of file diff --git a/template/codellama-70b-instruct.gotmpl b/template/codellama-70b-instruct.gotmpl index 392d839eb..0a313d389 100644 --- a/template/codellama-70b-instruct.gotmpl +++ b/template/codellama-70b-instruct.gotmpl @@ -7,13 +7,13 @@ {{ .Content }} {{ end }}Source: assistant Destination: user -{{ else }} -{{ if .System }} Source: system + {{ else -}} +{{ if .System }}Source: system - {{ .System }} {{ end }} Source: user + {{ .System }} {{ end }}Source: user {{ .Prompt }} Source: assistant Destination: user - {{ .Response }} -{{- end }} \ No newline at end of file + {{ .Response }} +{{- end -}} \ No newline at end of file diff --git a/template/falcon-instruct.gotmpl b/template/falcon-instruct.gotmpl index 99d67f93c..3a403007e 100644 --- a/template/falcon-instruct.gotmpl +++ b/template/falcon-instruct.gotmpl @@ -6,8 +6,10 @@ {{ else if eq .Role "assistant" }}Falcon: {{ end }}{{ .Content }} {{ end }}Falcon: -{{ else }} -{{ if .System }}{{ .System }} -{{ end }}{{ if .Prompt }}User: {{ .Prompt }} -{{ end }}Assistant: {{ .Response }} -{{- end }} \ No newline at end of file +{{ else -}} +{{ if .System }}System: {{ .System }} +{{ end }}{{ if .Prompt }}User: +{{ .Prompt }} +{{ end }}Falcon: +{{ .Response }} +{{ end -}} \ No newline at end of file diff --git a/template/gemma-instruct.gotmpl b/template/gemma-instruct.gotmpl index 870a8f2e2..6d778a70f 100644 --- a/template/gemma-instruct.gotmpl +++ b/template/gemma-instruct.gotmpl @@ -8,9 +8,10 @@ {{- end }} {{ .Content }} {{ end }}model -{{ else }} +{{ else -}} user -{{ if .System }}{{ .System }} {{ end }}{{ .Prompt }} +{{ if .System }}{{ .System }} +{{ end }}{{ .Prompt }} model {{ .Response }} -{{- end }} \ No newline at end of file +{{ end -}} \ No newline at end of file diff --git a/template/granite-instruct.gotmpl b/template/granite-instruct.gotmpl index 327ff3eef..4a85a97be 100644 --- a/template/granite-instruct.gotmpl +++ b/template/granite-instruct.gotmpl @@ -10,9 +10,8 @@ {{ .Content }} {{ end }}Answer: -{{ else }} -{{ if .System }} -System: +{{ else -}} +{{ if .System }}System: {{ .System }} {{ end }}{{ if .Prompt }}Question: @@ -20,4 +19,5 @@ System: {{ end }}Answer: {{ .Response }} -{{- end }} \ No newline at end of file + +{{ end -}} \ No newline at end of file diff --git a/template/llama2-chat.gotmpl b/template/llama2-chat.gotmpl index 6327d5812..1816fefd8 100644 --- a/template/llama2-chat.gotmpl +++ b/template/llama2-chat.gotmpl @@ -9,8 +9,8 @@ {{- else }} [/INST] {{ .Content }} {{- end }} {{- end }} [/INST] -{{- else }} -[INST] <>{{ .System }}<> +{{- else -}} +[INST] <>{{ if .System }}{{ .System }}{{ end }}<> -{{ .Prompt }} [/INST] {{ .Response }} -{{- end }} \ No newline at end of file +{{ .Prompt }} [/INST] {{ .Response }} +{{- end -}} \ No newline at end of file diff --git a/template/llama3-instruct.gotmpl b/template/llama3-instruct.gotmpl index 9c81a9535..7947b8da5 100644 --- a/template/llama3-instruct.gotmpl +++ b/template/llama3-instruct.gotmpl @@ -8,7 +8,7 @@ {{ .Content }}<|eot_id|> {{- end }}<|start_header_id|>assistant<|end_header_id|> -{{ else }} +{{ else -}} {{ if .System }}<|start_header_id|>system<|end_header_id|> {{ .System }}<|eot_id|>{{ end }}{{ if .Prompt }}<|start_header_id|>user<|end_header_id|> @@ -16,4 +16,4 @@ {{ .Prompt }}<|eot_id|>{{ end }}<|start_header_id|>assistant<|end_header_id|> {{ .Response }}<|eot_id|> -{{- end }} \ No newline at end of file +{{- end -}} \ No newline at end of file diff --git a/template/magicoder.gotmpl b/template/magicoder.gotmpl index 73a58127c..9227b6661 100644 --- a/template/magicoder.gotmpl +++ b/template/magicoder.gotmpl @@ -9,7 +9,7 @@ {{ .Content }} {{ end }}@@ Response -{{ else }} +{{ else -}} {{ if .System }}{{ .System }} {{ end }}{{ if .Prompt }}@@ Instruction @@ -17,4 +17,5 @@ {{ end }}@@ Response {{ .Response }} -{{- end }} \ No newline at end of file + +{{ end -}} \ No newline at end of file diff --git a/template/mistral-instruct.gotmpl b/template/mistral-instruct.gotmpl index eb3d5ced2..1d746dfd2 100644 --- a/template/mistral-instruct.gotmpl +++ b/template/mistral-instruct.gotmpl @@ -5,5 +5,6 @@ {{- else if eq .Role "assistant" }}[/INST] {{ .Content }} {{- end }} {{- end }}[/INST] -{{- else }}[INST] {{ if .System }}{{ .System }} {{ end }}{{ .Prompt }} [/INST] {{ .Response }} -{{- end }} \ No newline at end of file +{{- else -}} +[INST] {{ if .System }}{{ .System }} {{ end }}{{ .Prompt }}[/INST] {{ .Response }} +{{- end -}} \ No newline at end of file diff --git a/template/openchat.gotmpl b/template/openchat.gotmpl index d5e1cbb0d..649f0509c 100644 --- a/template/openchat.gotmpl +++ b/template/openchat.gotmpl @@ -1,11 +1,11 @@ {{- if .Messages }} -{{- if .System }}GPT Correct System: {{ .System }}<|end_of_turn|> +{{- if .System }}GPT4 Correct System: {{ .System }}<|end_of_turn|> {{- end }} -{{- range .Messages }}GPT Correct +{{- range .Messages }}GPT4 Correct {{- if eq .Role "user" }} User: {{- else if eq .Role "assistant" }} Assistant: {{- end }} {{ .Content }}<|end_of_turn|> -{{- end }}GPT Correct Assistant: -{{- else }} -{{ .System }}<|end_of_turn|>GPT4 Correct User: {{ .Prompt }}<|end_of_turn|>GPT4 Correct Assistant: {{ .Response }}<|end_of_turn|> -{{- end }} \ No newline at end of file +{{- end }}GPT4 Correct Assistant: +{{- else -}} +{{ if .System }}GPT4 Correct System: {{ .System }}<|end_of_turn|>{{ end }}GPT4 Correct User: {{ .Prompt }}<|end_of_turn|>GPT4 Correct Assistant: {{ .Response }}<|end_of_turn|> +{{- end -}} \ No newline at end of file diff --git a/template/phi-3.gotmpl b/template/phi-3.gotmpl index a3558d2b7..4ca56e952 100644 --- a/template/phi-3.gotmpl +++ b/template/phi-3.gotmpl @@ -5,11 +5,11 @@ {{- range .Messages }}<|{{ .Role }}|> {{ .Content }}<|end|> {{ end }}<|assistant|> -{{ else }} +{{ else -}} {{ if .System }}<|system|> {{ .System }}<|end|> {{ end }}{{ if .Prompt }}<|user|> {{ .Prompt }}<|end|> {{ end }}<|assistant|> {{ .Response }}<|end|> -{{- end }} \ No newline at end of file +{{ end -}} \ No newline at end of file diff --git a/template/solar-instruct.gotmpl b/template/solar-instruct.gotmpl index caa6e8e77..8a8331ca4 100644 --- a/template/solar-instruct.gotmpl +++ b/template/solar-instruct.gotmpl @@ -10,7 +10,7 @@ {{ .Content }} {{ end }} {{ end }}### Assistant: -{{ else }} +{{ else -}} {{ if .System }}### System: {{ .System }} @@ -18,5 +18,6 @@ {{ .Prompt }} {{ end }}### Assistant: -{{ .Response }} -{{- end }} \ No newline at end of file +{{ .Response }} + +{{ end -}} \ No newline at end of file diff --git a/template/starcoder2-instruct.gotmpl b/template/starcoder2-instruct.gotmpl index 7d7ff9326..17c6ad755 100644 --- a/template/starcoder2-instruct.gotmpl +++ b/template/starcoder2-instruct.gotmpl @@ -11,14 +11,13 @@ {{ end }} {{- end }}### Response -{{ else }} +{{ else -}} {{ if .System }}{{ .System }} {{ end }}{{ if .Prompt }}### Instruction {{ .Prompt }} - {{ end }}### Response {{ .Response }}<|endoftext|> -{{- end }} \ No newline at end of file +{{ end -}} \ No newline at end of file diff --git a/template/testdata/alpaca.gotmpl/system-user-assistant-user b/template/testdata/alpaca.gotmpl/system-user-assistant-user index 20182d829..4caa81788 100644 --- a/template/testdata/alpaca.gotmpl/system-user-assistant-user +++ b/template/testdata/alpaca.gotmpl/system-user-assistant-user @@ -1,4 +1,6 @@ -You are a helpful assistant.### Instruction: +You are a helpful assistant. + +### Instruction: Hello, how are you? ### Response: diff --git a/template/testdata/codellama-70b-instruct.gotmpl/system-user-assistant-user b/template/testdata/codellama-70b-instruct.gotmpl/system-user-assistant-user index fdd0fc8b4..d7528f80c 100644 --- a/template/testdata/codellama-70b-instruct.gotmpl/system-user-assistant-user +++ b/template/testdata/codellama-70b-instruct.gotmpl/system-user-assistant-user @@ -9,3 +9,4 @@ Source: system I'd like to show off how chat templating works! Source: assistant Destination: user + \ No newline at end of file diff --git a/template/testdata/codellama-70b-instruct.gotmpl/user b/template/testdata/codellama-70b-instruct.gotmpl/user index 9e7174a84..8e07853ca 100644 --- a/template/testdata/codellama-70b-instruct.gotmpl/user +++ b/template/testdata/codellama-70b-instruct.gotmpl/user @@ -3,3 +3,4 @@ Source: user Hello, how are you? Source: assistant Destination: user + \ No newline at end of file diff --git a/template/testdata/codellama-70b-instruct.gotmpl/user-assistant-user b/template/testdata/codellama-70b-instruct.gotmpl/user-assistant-user index b4ba1736b..f732cc746 100644 --- a/template/testdata/codellama-70b-instruct.gotmpl/user-assistant-user +++ b/template/testdata/codellama-70b-instruct.gotmpl/user-assistant-user @@ -7,3 +7,4 @@ Source: user I'd like to show off how chat templating works! Source: assistant Destination: user + \ No newline at end of file diff --git a/template/testdata/openchat.gotmpl/system-user-assistant-user b/template/testdata/openchat.gotmpl/system-user-assistant-user index 1214c1264..404b071aa 100644 --- a/template/testdata/openchat.gotmpl/system-user-assistant-user +++ b/template/testdata/openchat.gotmpl/system-user-assistant-user @@ -1 +1 @@ -GPT Correct System: You are a helpful assistant.<|end_of_turn|>GPT Correct User: Hello, how are you?<|end_of_turn|>GPT Correct Assistant: I'm doing great. How can I help you today?<|end_of_turn|>GPT Correct User: I'd like to show off how chat templating works!<|end_of_turn|>GPT Correct Assistant: \ No newline at end of file +GPT4 Correct System: You are a helpful assistant.<|end_of_turn|>GPT4 Correct User: Hello, how are you?<|end_of_turn|>GPT4 Correct Assistant: I'm doing great. How can I help you today?<|end_of_turn|>GPT4 Correct User: I'd like to show off how chat templating works!<|end_of_turn|>GPT4 Correct Assistant: \ No newline at end of file diff --git a/template/testdata/openchat.gotmpl/user b/template/testdata/openchat.gotmpl/user index 611daa83e..48229cb0e 100644 --- a/template/testdata/openchat.gotmpl/user +++ b/template/testdata/openchat.gotmpl/user @@ -1 +1 @@ -GPT Correct User: Hello, how are you?<|end_of_turn|>GPT Correct Assistant: \ No newline at end of file +GPT4 Correct User: Hello, how are you?<|end_of_turn|>GPT4 Correct Assistant: \ No newline at end of file diff --git a/template/testdata/openchat.gotmpl/user-assistant-user b/template/testdata/openchat.gotmpl/user-assistant-user index f97b02b9c..4719abb2d 100644 --- a/template/testdata/openchat.gotmpl/user-assistant-user +++ b/template/testdata/openchat.gotmpl/user-assistant-user @@ -1 +1 @@ -GPT Correct User: Hello, how are you?<|end_of_turn|>GPT Correct Assistant: I'm doing great. How can I help you today?<|end_of_turn|>GPT Correct User: I'd like to show off how chat templating works!<|end_of_turn|>GPT Correct Assistant: \ No newline at end of file +GPT4 Correct User: Hello, how are you?<|end_of_turn|>GPT4 Correct Assistant: I'm doing great. How can I help you today?<|end_of_turn|>GPT4 Correct User: I'd like to show off how chat templating works!<|end_of_turn|>GPT4 Correct Assistant: \ No newline at end of file diff --git a/template/vicuna.gotmpl b/template/vicuna.gotmpl index 2e13e990d..01465b997 100644 --- a/template/vicuna.gotmpl +++ b/template/vicuna.gotmpl @@ -7,8 +7,9 @@ {{ else if eq .Role "assistant" }}ASSISTANT: {{ .Content }} {{ end }} {{- end }}ASSISTANT: -{{- else }} +{{- else -}} {{ if .System }}{{ .System }} + {{ end }}{{ if .Prompt }}USER: {{ .Prompt }} -{{ end }}ASSISTANT: {{ .Response }} -{{- end }} \ No newline at end of file +{{ end }}ASSISTANT: {{ .Response }} +{{ end -}} \ No newline at end of file diff --git a/template/zephyr.gotmpl b/template/zephyr.gotmpl index e66688480..3ca1d1a1c 100644 --- a/template/zephyr.gotmpl +++ b/template/zephyr.gotmpl @@ -5,11 +5,11 @@ {{- range .Messages }}<|{{ .Role }}|> {{ .Content }} {{ end }}<|assistant|> -{{ else }} +{{ else -}} {{ if .System }}<|system|> {{ .System }} {{ end }}{{ if .Prompt }}<|user|> {{ .Prompt }} {{ end }}<|assistant|> {{ .Response }} -{{- end }} \ No newline at end of file +{{ end -}} \ No newline at end of file From efbf41ed8151098b942c142e2522b9ab8364f97a Mon Sep 17 00:00:00 2001 From: Jeffrey Morgan Date: Wed, 10 Jul 2024 20:01:52 -0700 Subject: [PATCH 066/384] llm: dont link cuda with compat libs (#5621) --- llm/generate/gen_linux.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/llm/generate/gen_linux.sh b/llm/generate/gen_linux.sh index 5589f1ead..db2c6c30c 100755 --- a/llm/generate/gen_linux.sh +++ b/llm/generate/gen_linux.sh @@ -178,7 +178,7 @@ if [ -z "${OLLAMA_SKIP_CUDA_GENERATE}" -a -d "${CUDA_LIB_DIR}" ]; then CMAKE_CUDA_DEFS="-DGGML_CUDA=on -DCMAKE_CUDA_ARCHITECTURES=${CMAKE_CUDA_ARCHITECTURES} ${OLLAMA_CUSTOM_CUDA_DEFS}" echo "Building custom CUDA GPU" else - CMAKE_CUDA_DEFS="-DGGML_CUDA=on -DCMAKE_CUDA_FLAGS=-t8 -DCMAKE_CUDA_ARCHITECTURES=${CMAKE_CUDA_ARCHITECTURES} -DCMAKE_LIBRARY_PATH=/usr/local/cuda/compat" + CMAKE_CUDA_DEFS="-DGGML_CUDA=on -DCMAKE_CUDA_FLAGS=-t8 -DCMAKE_CUDA_ARCHITECTURES=${CMAKE_CUDA_ARCHITECTURES}" fi CMAKE_DEFS="${COMMON_CMAKE_DEFS} ${CMAKE_DEFS} ${ARM64_DEFS} ${CMAKE_CUDA_DEFS}" BUILD_DIR="../build/linux/${ARCH}/cuda${CUDA_VARIANT}" From 791650ddef9eb11e011506dbd5d22ed6bfcb6a10 Mon Sep 17 00:00:00 2001 From: Jeffrey Morgan Date: Thu, 11 Jul 2024 00:53:12 -0700 Subject: [PATCH 067/384] sched: only error when over-allocating system memory (#5626) --- llm/server.go | 9 +++++++++ server/sched.go | 37 ------------------------------------- 2 files changed, 9 insertions(+), 37 deletions(-) diff --git a/llm/server.go b/llm/server.go index aa504d193..07c58cfff 100644 --- a/llm/server.go +++ b/llm/server.go @@ -122,6 +122,15 @@ func NewLlamaServer(gpus gpu.GpuInfoList, model string, ggml *GGML, adapters, pr } } + // On linux, over-allocating CPU memory will almost always result in an error + if runtime.GOOS == "linux" { + systemMemoryRequired := estimate.TotalSize - estimate.VRAMSize + if systemMemoryRequired > systemTotalMemory { + slog.Warn("model request too large for system", "requested", format.HumanBytes2(systemMemoryRequired), "system", format.HumanBytes2(systemTotalMemory)) + return nil, fmt.Errorf("model requires more system memory (%s) than is available (%s)", format.HumanBytes2(systemMemoryRequired), format.HumanBytes2(systemTotalMemory)) + } + } + estimate.log() // Loop through potential servers diff --git a/server/sched.go b/server/sched.go index 48047bfec..2daed3abb 100644 --- a/server/sched.go +++ b/server/sched.go @@ -135,11 +135,6 @@ func (s *Scheduler) processPending(ctx context.Context) { } for { - cpus := s.getCpuFn() - var systemMem gpu.GpuInfo - if len(cpus) > 0 { - systemMem = cpus[0] - } var runnerToExpire *runnerRef s.loadedMu.Lock() runner := s.loaded[pending.model.ModelPath] @@ -193,38 +188,6 @@ func (s *Scheduler) processPending(ctx context.Context) { break } - estimate := llm.EstimateGPULayers(gpus, ggml, pending.model.ProjectorPaths, pending.opts) - maxSize := systemMem.FreeMemory - - // Add available GPU memory to the total pool - // macOS hardware has unified memory so don't double count - if runtime.GOOS != "darwin" { - for _, gpu := range gpus { - if gpu.Library == "cpu" { - continue - } - if loadedCount == 0 { - // If no other models are loaded, set the limit based on what's available - maxSize += gpu.FreeMemory - } else { - // Other models could be unloaded, favor total memory for limit - maxSize += gpu.TotalMemory - } - } - } - - // Block attempting to load a model larger than system memory + GPU memory - if estimate.TotalSize > maxSize { - slog.Warn("model request too large for system", "requested", format.HumanBytes2(estimate.TotalSize), "system", format.HumanBytes2(maxSize)) - - // Linux will crash if over-allocating memory - return an error to the user. - // TODO (jmorganca): add reasonable upper limits for darwin and windows as well - if runtime.GOOS == "linux" { - pending.errCh <- fmt.Errorf("requested model (%s) is too large for this system (%s)", format.HumanBytes2(estimate.TotalSize), format.HumanBytes2(maxSize)) - break - } - } - // Evaluate if the model will fit in the available system memory, or if we should unload a model first if len(gpus) == 1 && gpus[0].Library == "cpu" { // simplifying assumption of defaultParallel when in CPU mode From e64f9ebb44b584d94094274f62acd90a5195dd89 Mon Sep 17 00:00:00 2001 From: Michael Yang Date: Thu, 11 Jul 2024 13:10:13 -0700 Subject: [PATCH 068/384] do no automatically aggregate system messages --- template/template.go | 39 ++++++++++++++++++++------------------- template/template_test.go | 11 +++++++---- 2 files changed, 27 insertions(+), 23 deletions(-) diff --git a/template/template.go b/template/template.go index 0b8f24348..8d5ac51b8 100644 --- a/template/template.go +++ b/template/template.go @@ -102,8 +102,21 @@ var response = parse.ActionNode{ }, } +var funcs = template.FuncMap{ + "aggregate": func(v []*api.Message, role string) string { + var aggregated []string + for _, m := range v { + if m.Role == role { + aggregated = append(aggregated, m.Content) + } + } + + return strings.Join(aggregated, "\n\n") + }, +} + func Parse(s string) (*Template, error) { - tmpl := template.New("").Option("missingkey=zero") + tmpl := template.New("").Option("missingkey=zero").Funcs(funcs) tmpl, err := tmpl.Parse(s) if err != nil { @@ -149,23 +162,21 @@ type Values struct { } func (t *Template) Execute(w io.Writer, v Values) error { - system, collated := collate(v.Messages) + collated := collate(v.Messages) if !v.forceLegacy && slices.Contains(t.Vars(), "messages") { return t.Template.Execute(w, map[string]any{ - "System": system, "Messages": collated, }) } var b bytes.Buffer - var prompt, response string + var system, prompt, response string for i, m := range collated { switch m.Role { + case "system": + system = m.Content case "user": prompt = m.Content - if i != 0 { - system = "" - } case "assistant": response = m.Content } @@ -179,6 +190,7 @@ func (t *Template) Execute(w io.Writer, v Values) error { return err } + system = "" prompt = "" response = "" } @@ -209,25 +221,14 @@ func (t *Template) Execute(w io.Writer, v Values) error { return err } -type messages []*api.Message - // collate messages based on role. consecutive messages of the same role are merged // into a single message. collate also pulls out and merges messages with Role == "system" // which are templated separately. As a side effect, it mangles message content adding image // tags ([img-%d]) as needed -func collate(msgs []api.Message) (system string, collated messages) { +func collate(msgs []api.Message) (collated []*api.Message) { var n int for i := range msgs { msg := msgs[i] - if msg.Role == "system" { - if system != "" { - system += "\n\n" - } - - system += msg.Content - continue - } - for range msg.Images { imageTag := fmt.Sprintf("[img-%d]", n) if !strings.Contains(msg.Content, "[img]") { diff --git a/template/template_test.go b/template/template_test.go index e702a1862..b020eb67a 100644 --- a/template/template_test.go +++ b/template/template_test.go @@ -122,6 +122,7 @@ func TestTemplate(t *testing.T) { }) t.Run("legacy", func(t *testing.T) { + t.Skip("legacy outputs are currently default outputs") var legacy bytes.Buffer if err := tmpl.Execute(&legacy, Values{Messages: tt, forceLegacy: true}); err != nil { t.Fatal(err) @@ -154,11 +155,13 @@ func TestParse(t *testing.T) { {"{{ .System }} {{ .Prompt }} {{ .Response }}", []string{"prompt", "response", "system"}}, {"{{ with .Tools }}{{ . }}{{ end }} {{ .System }} {{ .Prompt }}", []string{"prompt", "response", "system", "tools"}}, {"{{ range .Messages }}{{ .Role }} {{ .Content }}{{ end }}", []string{"content", "messages", "role"}}, - {"{{ range .Messages }}{{ if eq .Role \"system\" }}SYSTEM: {{ .Content }}{{ else if eq .Role \"user\" }}USER: {{ .Content }}{{ else if eq .Role \"assistant\" }}ASSISTANT: {{ .Content }}{{ end }}{{ end }}", []string{"content", "messages", "role"}}, + {`{{- range .Messages }} +{{- if eq .Role "system" }}SYSTEM: +{{- else if eq .Role "user" }}USER: +{{- else if eq .Role "assistant" }}ASSISTANT: +{{- end }} {{ .Content }} +{{- end }}`, []string{"content", "messages", "role"}}, {`{{- if .Messages }} -{{- if .System }}<|im_start|>system -{{ .System }}<|im_end|> -{{ end }} {{- range .Messages }}<|im_start|>{{ .Role }} {{ .Content }}<|im_end|> {{ end }}<|im_start|>assistant From 57ec6901eb59cca9d0c29adca3f0fd4b95c1c989 Mon Sep 17 00:00:00 2001 From: Michael Yang Date: Thu, 11 Jul 2024 13:11:40 -0700 Subject: [PATCH 069/384] revert embedded templates to use prompt/response This reverts commit 19753c18c01183b4c974e36e89b0c7cbdcc3c38a. for compat. messages will be added at a later date --- server/routes_create_test.go | 4 +- template/alfred.gotmpl | 9 +-- template/alpaca.gotmpl | 13 ---- template/chatml.gotmpl | 9 --- template/chatqa.gotmpl | 12 ---- template/codellama-70b-instruct.gotmpl | 15 +---- template/falcon-instruct.gotmpl | 10 ---- template/gemma-instruct.gotmpl | 12 ---- template/granite-instruct.gotmpl | 14 ----- template/llama2-chat.gotmpl | 18 ++---- template/llama3-instruct.gotmpl | 14 +---- template/magicoder.gotmpl | 13 ---- template/mistral-instruct.gotmpl | 13 +--- template/openchat.gotmpl | 12 +--- template/phi-3.gotmpl | 9 --- template/solar-instruct.gotmpl | 14 ----- template/starcoder2-instruct.gotmpl | 15 ----- template/template_test.go | 59 ++++++++++++------- .../system-user-assistant-user | 4 +- .../llama2-chat.gotmpl/user-assistant-user | 4 +- .../system-user-assistant-user | 5 +- template/vicuna.gotmpl | 11 ---- template/zephyr.gotmpl | 9 --- 23 files changed, 63 insertions(+), 235 deletions(-) diff --git a/server/routes_create_test.go b/server/routes_create_test.go index 40477937b..04174b92e 100644 --- a/server/routes_create_test.go +++ b/server/routes_create_test.go @@ -546,8 +546,8 @@ func TestCreateDetectTemplate(t *testing.T) { checkFileExists(t, filepath.Join(p, "blobs", "*"), []string{ filepath.Join(p, "blobs", "sha256-553c4a3f747b3d22a4946875f1cc8ed011c2930d83f864a0c7265f9ec0a20413"), - filepath.Join(p, "blobs", "sha256-68b0323b2f21572bc09ba07554b16b379a5713ee48ef8c25a7661a1f71cfce77"), - filepath.Join(p, "blobs", "sha256-eb72fb7c550ee1f1dec4039bd65382acecf5f7536a30fb7ccace39a8d0cb590b"), + filepath.Join(p, "blobs", "sha256-c608dc615584cd20d9d830363dabf8a4783ae5d34245c3d8c115edb3bc7b28e4"), + filepath.Join(p, "blobs", "sha256-f836ee110db21567f826332e4cedd746c06d10664fd5a9ea3659e3683a944510"), }) }) diff --git a/template/alfred.gotmpl b/template/alfred.gotmpl index 71bc6706f..cecb9d2c8 100644 --- a/template/alfred.gotmpl +++ b/template/alfred.gotmpl @@ -1,8 +1 @@ -{{- if .Messages }} -{{- if .System }}{{ .System }} -{{- end }} -{{- range .Messages }}{{ .Content }} -{{- end }} -{{- else -}} -{{ if .System }}{{ .System }}{{ end }}{{ if .Prompt }}{{ .Prompt }}{{ end }}{{ .Response }} -{{- end -}} \ No newline at end of file +{{ if .System }}{{ .System }}{{ end }}{{ if .Prompt }}{{ .Prompt }}{{ end }}{{ .Response }} \ No newline at end of file diff --git a/template/alpaca.gotmpl b/template/alpaca.gotmpl index e9becb3d4..ec7a8edcb 100644 --- a/template/alpaca.gotmpl +++ b/template/alpaca.gotmpl @@ -1,15 +1,3 @@ -{{- if .Messages }} -{{- if .System }}{{ .System }} - -{{ end }} -{{- range .Messages }} -{{- if eq .Role "user" }}### Instruction: -{{- else if eq .Role "assistant" }}### Response: -{{- end }} -{{ .Content }} - -{{ end }}### Response: -{{ else -}} {{ if .System }}{{ .System }} {{ end }}{{ if .Prompt }}### Instruction: @@ -18,4 +6,3 @@ {{ end }}### Response: {{ .Response }} -{{ end -}} \ No newline at end of file diff --git a/template/chatml.gotmpl b/template/chatml.gotmpl index eb8ab0dcd..fb672601a 100644 --- a/template/chatml.gotmpl +++ b/template/chatml.gotmpl @@ -1,15 +1,6 @@ -{{- if .Messages }} -{{- if .System }}<|im_start|>system -{{ .System }}<|im_end|> -{{ end }} -{{- range .Messages }}<|im_start|>{{ .Role }} -{{ .Content }}<|im_end|> -{{ end }}<|im_start|>assistant -{{ else -}} {{ if .System }}<|im_start|>system {{ .System }}<|im_end|> {{ end }}{{ if .Prompt }}<|im_start|>user {{ .Prompt }}<|im_end|> {{ end }}<|im_start|>assistant {{ .Response }}<|im_end|> -{{ end -}} \ No newline at end of file diff --git a/template/chatqa.gotmpl b/template/chatqa.gotmpl index 41c6ced59..91679a72d 100644 --- a/template/chatqa.gotmpl +++ b/template/chatqa.gotmpl @@ -1,18 +1,6 @@ -{{- if .Messages }} -{{- if .System }}System: {{ .System }} - -{{ end }} -{{- range .Messages }} -{{- if eq .Role "user" }}User: -{{- else if eq .Role "assistant" }}Assistant: -{{- end }} {{ .Content }} - -{{ end }}Assistant: -{{- else -}} {{ if .System }}System: {{ .System }} {{ end }}{{ if .Prompt }}User: {{ .Prompt }} {{ end }}Assistant: {{ .Response }} -{{ end -}} \ No newline at end of file diff --git a/template/codellama-70b-instruct.gotmpl b/template/codellama-70b-instruct.gotmpl index 0a313d389..e5856042c 100644 --- a/template/codellama-70b-instruct.gotmpl +++ b/template/codellama-70b-instruct.gotmpl @@ -1,19 +1,10 @@ -{{- if .Messages }} -{{- if .System }}Source: system - - {{ .System }} {{ end }} -{{- range .Messages }}Source: {{ .Role }} - - {{ .Content }} {{ end }}Source: assistant -Destination: user - - {{ else -}} {{ if .System }}Source: system {{ .System }} {{ end }}Source: user {{ .Prompt }} Source: assistant +{{- if not .Response }} Destination: user +{{- end }} - {{ .Response }} -{{- end -}} \ No newline at end of file + {{ .Response }} \ No newline at end of file diff --git a/template/falcon-instruct.gotmpl b/template/falcon-instruct.gotmpl index 3a403007e..0a5fe48e8 100644 --- a/template/falcon-instruct.gotmpl +++ b/template/falcon-instruct.gotmpl @@ -1,15 +1,5 @@ -{{- if .Messages }} -{{- if .System }}System: {{ .System }} -{{ end }} -{{- range .Messages }} -{{- if eq .Role "user" }}User: -{{ else if eq .Role "assistant" }}Falcon: -{{ end }}{{ .Content }} -{{ end }}Falcon: -{{ else -}} {{ if .System }}System: {{ .System }} {{ end }}{{ if .Prompt }}User: {{ .Prompt }} {{ end }}Falcon: {{ .Response }} -{{ end -}} \ No newline at end of file diff --git a/template/gemma-instruct.gotmpl b/template/gemma-instruct.gotmpl index 6d778a70f..3c3a84256 100644 --- a/template/gemma-instruct.gotmpl +++ b/template/gemma-instruct.gotmpl @@ -1,17 +1,5 @@ -{{- if .Messages }} -{{- range $index, $_ := .Messages }} -{{- if eq .Role "user" }}user -{{- if and $.System (eq $index 0) }} -{{ $.System }} -{{- end }} -{{- else if eq .Role "assistant" }}model -{{- end }} -{{ .Content }} -{{ end }}model -{{ else -}} user {{ if .System }}{{ .System }} {{ end }}{{ .Prompt }} model {{ .Response }} -{{ end -}} \ No newline at end of file diff --git a/template/granite-instruct.gotmpl b/template/granite-instruct.gotmpl index 4a85a97be..56690fce6 100644 --- a/template/granite-instruct.gotmpl +++ b/template/granite-instruct.gotmpl @@ -1,16 +1,3 @@ -{{- if .Messages }} -{{- if .System }}System: -{{ .System }} - -{{ end }} -{{- range .Messages }} -{{- if eq .Role "user" }}Question: -{{- else if eq .Role "assistant" }}Answer: -{{- end }} -{{ .Content }} - -{{ end }}Answer: -{{ else -}} {{ if .System }}System: {{ .System }} @@ -20,4 +7,3 @@ {{ end }}Answer: {{ .Response }} -{{ end -}} \ No newline at end of file diff --git a/template/llama2-chat.gotmpl b/template/llama2-chat.gotmpl index 1816fefd8..013b414e2 100644 --- a/template/llama2-chat.gotmpl +++ b/template/llama2-chat.gotmpl @@ -1,16 +1,6 @@ -{{- if .Messages }} -{{- range $index, $_ := .Messages }} -{{- if eq .Role "user" }}[INST] {{ if eq $index 0 }}<> -{{- if $.System }} -{{ $.System }} +[INST] <> +{{- if .System }} +{{ .System }} {{ end }}<> -{{ end }}{{ .Content }} -{{- else }} [/INST] {{ .Content }} -{{- end }} -{{- end }} [/INST] -{{- else -}} -[INST] <>{{ if .System }}{{ .System }}{{ end }}<> - -{{ .Prompt }} [/INST] {{ .Response }} -{{- end -}} \ No newline at end of file +{{ .Prompt }} [/INST] {{ .Response }} \ No newline at end of file diff --git a/template/llama3-instruct.gotmpl b/template/llama3-instruct.gotmpl index 7947b8da5..36d0218b6 100644 --- a/template/llama3-instruct.gotmpl +++ b/template/llama3-instruct.gotmpl @@ -1,19 +1,7 @@ -{{- if .Messages }} -{{- if .System }}<|start_header_id|>system<|end_header_id|> - -{{ .System }}<|eot_id|> -{{- end }} -{{- range .Messages }}<|start_header_id|>{{ .Role }}<|end_header_id|> - -{{ .Content }}<|eot_id|> -{{- end }}<|start_header_id|>assistant<|end_header_id|> - -{{ else -}} {{ if .System }}<|start_header_id|>system<|end_header_id|> {{ .System }}<|eot_id|>{{ end }}{{ if .Prompt }}<|start_header_id|>user<|end_header_id|> {{ .Prompt }}<|eot_id|>{{ end }}<|start_header_id|>assistant<|end_header_id|> -{{ .Response }}<|eot_id|> -{{- end -}} \ No newline at end of file +{{ .Response }}<|eot_id|> \ No newline at end of file diff --git a/template/magicoder.gotmpl b/template/magicoder.gotmpl index 9227b6661..52abc01aa 100644 --- a/template/magicoder.gotmpl +++ b/template/magicoder.gotmpl @@ -1,15 +1,3 @@ -{{- if .Messages }} -{{- if .System }}{{ .System }} - -{{ end }} -{{- range .Messages }} -{{- if eq .Role "user" }}@@ Instruction -{{- else if eq .Role "assistant" }}@@ Response -{{- end }} -{{ .Content }} - -{{ end }}@@ Response -{{ else -}} {{ if .System }}{{ .System }} {{ end }}{{ if .Prompt }}@@ Instruction @@ -18,4 +6,3 @@ {{ end }}@@ Response {{ .Response }} -{{ end -}} \ No newline at end of file diff --git a/template/mistral-instruct.gotmpl b/template/mistral-instruct.gotmpl index 1d746dfd2..e489bd4c5 100644 --- a/template/mistral-instruct.gotmpl +++ b/template/mistral-instruct.gotmpl @@ -1,10 +1,3 @@ -{{- if .Messages }} -{{- range $index, $_ := .Messages }} -{{- if eq .Role "user" }}[INST] {{ if and $.System (eq (len (slice $.Messages $index)) 1) }}{{ $.System }} -{{ end }}{{ .Content }} -{{- else if eq .Role "assistant" }}[/INST] {{ .Content }} -{{- end }} -{{- end }}[/INST] -{{- else -}} -[INST] {{ if .System }}{{ .System }} {{ end }}{{ .Prompt }}[/INST] {{ .Response }} -{{- end -}} \ No newline at end of file +[INST] {{ if .System }}{{ .System }} + +{{ end }}{{ .Prompt }}[/INST] {{ .Response }} \ No newline at end of file diff --git a/template/openchat.gotmpl b/template/openchat.gotmpl index 649f0509c..9c1838343 100644 --- a/template/openchat.gotmpl +++ b/template/openchat.gotmpl @@ -1,11 +1 @@ -{{- if .Messages }} -{{- if .System }}GPT4 Correct System: {{ .System }}<|end_of_turn|> -{{- end }} -{{- range .Messages }}GPT4 Correct -{{- if eq .Role "user" }} User: -{{- else if eq .Role "assistant" }} Assistant: -{{- end }} {{ .Content }}<|end_of_turn|> -{{- end }}GPT4 Correct Assistant: -{{- else -}} -{{ if .System }}GPT4 Correct System: {{ .System }}<|end_of_turn|>{{ end }}GPT4 Correct User: {{ .Prompt }}<|end_of_turn|>GPT4 Correct Assistant: {{ .Response }}<|end_of_turn|> -{{- end -}} \ No newline at end of file +{{ if .System }}GPT4 Correct System: {{ .System }}<|end_of_turn|>{{ end }}GPT4 Correct User: {{ .Prompt }}<|end_of_turn|>GPT4 Correct Assistant: {{ .Response }}<|end_of_turn|> \ No newline at end of file diff --git a/template/phi-3.gotmpl b/template/phi-3.gotmpl index 4ca56e952..6c3610dda 100644 --- a/template/phi-3.gotmpl +++ b/template/phi-3.gotmpl @@ -1,15 +1,6 @@ -{{- if .Messages }} -{{- if .System }}<|system|> -{{ .System }}<|end|> -{{ end }} -{{- range .Messages }}<|{{ .Role }}|> -{{ .Content }}<|end|> -{{ end }}<|assistant|> -{{ else -}} {{ if .System }}<|system|> {{ .System }}<|end|> {{ end }}{{ if .Prompt }}<|user|> {{ .Prompt }}<|end|> {{ end }}<|assistant|> {{ .Response }}<|end|> -{{ end -}} \ No newline at end of file diff --git a/template/solar-instruct.gotmpl b/template/solar-instruct.gotmpl index 8a8331ca4..1c14960d4 100644 --- a/template/solar-instruct.gotmpl +++ b/template/solar-instruct.gotmpl @@ -1,16 +1,3 @@ -{{- if .Messages }} -{{- if .System }}### System: -{{ .System }} - -{{ end }} -{{- range .Messages }} -{{- if eq .Role "user" }}### User: -{{ .Content }} -{{ else if eq .Role "assistant" }}### Assistant: -{{ .Content }} -{{ end }} -{{ end }}### Assistant: -{{ else -}} {{ if .System }}### System: {{ .System }} @@ -20,4 +7,3 @@ {{ end }}### Assistant: {{ .Response }} -{{ end -}} \ No newline at end of file diff --git a/template/starcoder2-instruct.gotmpl b/template/starcoder2-instruct.gotmpl index 17c6ad755..6c93a7abc 100644 --- a/template/starcoder2-instruct.gotmpl +++ b/template/starcoder2-instruct.gotmpl @@ -1,17 +1,3 @@ -{{- if .Messages }} -{{- if .System }}{{ .System }} - -{{ end }} -{{- range .Messages }} -{{- if eq .Role "user" }}### Instruction -{{ .Content }} - -{{ else if eq .Role "assistant" }}### Response -{{ .Content }}<|endoftext|> - -{{ end }} -{{- end }}### Response -{{ else -}} {{ if .System }}{{ .System }} {{ end }}{{ if .Prompt }}### Instruction @@ -20,4 +6,3 @@ {{ end }}### Response {{ .Response }}<|endoftext|> -{{ end -}} \ No newline at end of file diff --git a/template/template_test.go b/template/template_test.go index b020eb67a..9cfa0beaa 100644 --- a/template/template_test.go +++ b/template/template_test.go @@ -116,7 +116,14 @@ func TestTemplate(t *testing.T) { t.Fatal(err) } - if diff := cmp.Diff(actual.Bytes(), expect); diff != "" { + bts := actual.Bytes() + + if slices.Contains([]string{"chatqa.gotmpl", "llama2-chat.gotmpl", "mistral-instruct.gotmpl", "openchat.gotmpl", "vicuna.gotmpl"}, match) && bts[len(bts)-1] == ' ' { + t.Log("removing trailing space from output") + bts = bts[:len(bts)-1] + } + + if diff := cmp.Diff(bts, expect); diff != "" { t.Errorf("mismatch (-got +want):\n%s", diff) } }) @@ -203,11 +210,18 @@ func TestExecuteWithMessages(t *testing.T) { { "mistral", []template{ - {"no response", `[INST] {{ if .System }}{{ .System }}{{ "\n\n" }}{{ end }}{{ .Prompt }}[/INST] `}, - {"response", `[INST] {{ if .System }}{{ .System }}{{ "\n\n" }}{{ end }}{{ .Prompt }}[/INST] {{ .Response }}`}, - {"messages", `{{- range $index, $_ := .Messages }} -{{- if eq .Role "user" }}[INST] {{ if and (eq $index 0) $.System }}{{ $.System }}{{ "\n\n" }} -{{- end }}{{ .Content }}[/INST] {{ else if eq .Role "assistant" }}{{ .Content }} + {"no response", `[INST] {{ if .System }}{{ .System }} + +{{ end }}{{ .Prompt }}[/INST] `}, + {"response", `[INST] {{ if .System }}{{ .System }} + +{{ end }}{{ .Prompt }}[/INST] {{ .Response }}`}, + {"messages", `{{- $system := aggregate $.Messages "system" -}} +{{- range $index, $_ := .Messages }} +{{- if eq .Role "user" }}[INST] {{ if $system }}{{ $system }} +{{- $system = "" }} + +{{ end }}{{ .Content }}[/INST] {{ else if eq .Role "assistant" }}{{ .Content }} {{- end }} {{- end }}`}, }, @@ -223,12 +237,18 @@ func TestExecuteWithMessages(t *testing.T) { { "mistral system", []template{ - {"no response", `[INST] {{ if .System }}{{ .System }}{{ "\n\n" }}{{ end }}{{ .Prompt }}[/INST] `}, - {"response", `[INST] {{ if .System }}{{ .System }}{{ "\n\n" }}{{ end }}{{ .Prompt }}[/INST] {{ .Response }}`}, - {"messages", ` + {"no response", `[INST] {{ if .System }}{{ .System }} + +{{ end }}{{ .Prompt }}[/INST] `}, + {"response", `[INST] {{ if .System }}{{ .System }} + +{{ end }}{{ .Prompt }}[/INST] {{ .Response }}`}, + {"messages", `{{- $system := aggregate $.Messages "system" -}} {{- range $index, $_ := .Messages }} -{{- if eq .Role "user" }}[INST] {{ if and (eq $index 0) $.System }}{{ $.System }}{{ "\n\n" }} -{{- end }}{{ .Content }}[/INST] {{ else if eq .Role "assistant" }}{{ .Content }} +{{- if eq .Role "user" }}[INST] {{ if $system }}{{ $system }} +{{- $system = "" }} + +{{ end }}{{ .Content }}[/INST] {{ else if eq .Role "assistant" }}{{ .Content }} {{- end }} {{- end }}`}, }, @@ -256,12 +276,9 @@ Hello friend![/INST] Hello human![INST] What is your name?[/INST] `, {{ .Response }}<|im_end|> `}, {"messages", ` -{{- range $index, $_ := .Messages }} -{{- if and (eq .Role "user") (eq $index 0) $.System }}<|im_start|>system -{{ $.System }}<|im_end|>{{ "\n" }} -{{- end }}<|im_start|>{{ .Role }} -{{ .Content }}<|im_end|>{{ "\n" }} -{{- end }}<|im_start|>assistant +{{- range $index, $_ := .Messages }}<|im_start|>{{ .Role }} +{{ .Content }}<|im_end|> +{{ end }}<|im_start|>assistant `}, }, Values{ @@ -294,9 +311,11 @@ What is your name?<|im_end|> `}, {"messages", ` {{- range .Messages }} -{{- if eq .Role "user" }}Question: {{ .Content }}{{ "\n\n" }} -{{- else if eq .Role "assistant" }}Answer: {{ .Content }}{{ "\n\n" }} -{{- end }} +{{- if eq .Role "user" }}Question: {{ .Content }} + +{{ else if eq .Role "assistant" }}Answer: {{ .Content }} + +{{ end }} {{- end }}Answer: `}, }, Values{ diff --git a/template/testdata/llama2-chat.gotmpl/system-user-assistant-user b/template/testdata/llama2-chat.gotmpl/system-user-assistant-user index fc2679bf0..9db81cb44 100644 --- a/template/testdata/llama2-chat.gotmpl/system-user-assistant-user +++ b/template/testdata/llama2-chat.gotmpl/system-user-assistant-user @@ -2,4 +2,6 @@ You are a helpful assistant. <> -Hello, how are you? [/INST] I'm doing great. How can I help you today?[INST] I'd like to show off how chat templating works! [/INST] \ No newline at end of file +Hello, how are you? [/INST] I'm doing great. How can I help you today?[INST] <><> + +I'd like to show off how chat templating works! [/INST] \ No newline at end of file diff --git a/template/testdata/llama2-chat.gotmpl/user-assistant-user b/template/testdata/llama2-chat.gotmpl/user-assistant-user index 42b4c5294..ca58954f5 100644 --- a/template/testdata/llama2-chat.gotmpl/user-assistant-user +++ b/template/testdata/llama2-chat.gotmpl/user-assistant-user @@ -1,3 +1,5 @@ [INST] <><> -Hello, how are you? [/INST] I'm doing great. How can I help you today?[INST] I'd like to show off how chat templating works! [/INST] \ No newline at end of file +Hello, how are you? [/INST] I'm doing great. How can I help you today?[INST] <><> + +I'd like to show off how chat templating works! [/INST] \ No newline at end of file diff --git a/template/testdata/mistral-instruct.gotmpl/system-user-assistant-user b/template/testdata/mistral-instruct.gotmpl/system-user-assistant-user index b6b4bf93e..2f1edaec9 100644 --- a/template/testdata/mistral-instruct.gotmpl/system-user-assistant-user +++ b/template/testdata/mistral-instruct.gotmpl/system-user-assistant-user @@ -1,2 +1,3 @@ -[INST] Hello, how are you?[/INST] I'm doing great. How can I help you today?[INST] You are a helpful assistant. -I'd like to show off how chat templating works![/INST] \ No newline at end of file +[INST] You are a helpful assistant. + +Hello, how are you?[/INST] I'm doing great. How can I help you today?[INST] I'd like to show off how chat templating works![/INST] \ No newline at end of file diff --git a/template/vicuna.gotmpl b/template/vicuna.gotmpl index 01465b997..515b2fe94 100644 --- a/template/vicuna.gotmpl +++ b/template/vicuna.gotmpl @@ -1,15 +1,4 @@ -{{- if .Messages }} -{{- if .System }}{{ .System }} - -{{ end }} -{{- range .Messages }} -{{- if eq .Role "user" }}USER: {{ .Content }} -{{ else if eq .Role "assistant" }}ASSISTANT: {{ .Content }} -{{ end }} -{{- end }}ASSISTANT: -{{- else -}} {{ if .System }}{{ .System }} {{ end }}{{ if .Prompt }}USER: {{ .Prompt }} {{ end }}ASSISTANT: {{ .Response }} -{{ end -}} \ No newline at end of file diff --git a/template/zephyr.gotmpl b/template/zephyr.gotmpl index 3ca1d1a1c..1f889f267 100644 --- a/template/zephyr.gotmpl +++ b/template/zephyr.gotmpl @@ -1,15 +1,6 @@ -{{- if .Messages }} -{{- if .System }}<|system|> -{{ .System }} -{{ end }} -{{- range .Messages }}<|{{ .Role }}|> -{{ .Content }} -{{ end }}<|assistant|> -{{ else -}} {{ if .System }}<|system|> {{ .System }} {{ end }}{{ if .Prompt }}<|user|> {{ .Prompt }} {{ end }}<|assistant|> {{ .Response }} -{{ end -}} \ No newline at end of file From c4cf8ad55966cc61c73f119ab9cbfaf57264fc81 Mon Sep 17 00:00:00 2001 From: Jeffrey Morgan Date: Thu, 11 Jul 2024 16:42:57 -0700 Subject: [PATCH 070/384] llm: avoid loading model if system memory is too small (#5637) * llm: avoid loading model if system memory is too small * update log * Instrument swap free space On linux and windows, expose how much swap space is available so we can take that into consideration when scheduling models * use `systemSwapFreeMemory` in check --------- Co-authored-by: Daniel Hiltgen --- gpu/gpu.go | 3 +++ gpu/gpu_darwin.go | 1 + gpu/gpu_linux.go | 17 +++++++++-------- gpu/gpu_windows.go | 2 +- gpu/types.go | 1 + llm/server.go | 11 +++++++---- 6 files changed, 22 insertions(+), 13 deletions(-) diff --git a/gpu/gpu.go b/gpu/gpu.go index 58144991d..6e25cb46d 100644 --- a/gpu/gpu.go +++ b/gpu/gpu.go @@ -360,14 +360,17 @@ func GetGPUInfo() GpuInfoList { "before", "total", format.HumanBytes2(cpus[0].TotalMemory), "free", format.HumanBytes2(cpus[0].FreeMemory), + "free_swap", format.HumanBytes2(cpus[0].FreeSwap), ), slog.Group( "now", "total", format.HumanBytes2(mem.TotalMemory), "free", format.HumanBytes2(mem.FreeMemory), + "free_swap", format.HumanBytes2(mem.FreeSwap), ), ) cpus[0].FreeMemory = mem.FreeMemory + cpus[0].FreeSwap = mem.FreeSwap } var memInfo C.mem_info_t diff --git a/gpu/gpu_darwin.go b/gpu/gpu_darwin.go index 39d8fcf89..cb066e581 100644 --- a/gpu/gpu_darwin.go +++ b/gpu/gpu_darwin.go @@ -57,6 +57,7 @@ func GetCPUMem() (memInfo, error) { return memInfo{ TotalMemory: uint64(C.getPhysicalMemory()), FreeMemory: uint64(C.getFreeMemory()), + // FreeSwap omitted as Darwin uses dynamic paging }, nil } diff --git a/gpu/gpu_linux.go b/gpu/gpu_linux.go index a099bf822..0d08ce8da 100644 --- a/gpu/gpu_linux.go +++ b/gpu/gpu_linux.go @@ -50,7 +50,7 @@ var OneapiMgmtName = "libze_intel_gpu.so" func GetCPUMem() (memInfo, error) { var mem memInfo - var total, available, free, buffers, cached uint64 + var total, available, free, buffers, cached, freeSwap uint64 f, err := os.Open("/proc/meminfo") if err != nil { return mem, err @@ -70,20 +70,21 @@ func GetCPUMem() (memInfo, error) { _, err = fmt.Sscanf(line, "Buffers:%d", &buffers) case strings.HasPrefix(line, "Cached:"): _, err = fmt.Sscanf(line, "Cached:%d", &cached) + case strings.HasPrefix(line, "SwapFree:"): + _, err = fmt.Sscanf(line, "SwapFree:%d", &freeSwap) default: continue } if err != nil { return mem, err } - - if total > 0 && available > 0 { - mem.TotalMemory = total * format.KibiByte - mem.FreeMemory = available * format.KibiByte - return mem, nil - } } mem.TotalMemory = total * format.KibiByte - mem.FreeMemory = (free + buffers + cached) * format.KibiByte + mem.FreeSwap = freeSwap * format.KibiByte + if available > 0 { + mem.FreeMemory = available * format.KibiByte + } else { + mem.FreeMemory = (free + buffers + cached) * format.KibiByte + } return mem, nil } diff --git a/gpu/gpu_windows.go b/gpu/gpu_windows.go index f8c2e76fe..cd0629da4 100644 --- a/gpu/gpu_windows.go +++ b/gpu/gpu_windows.go @@ -51,5 +51,5 @@ func GetCPUMem() (memInfo, error) { if r1 == 0 { return memInfo{}, fmt.Errorf("GlobalMemoryStatusEx failed: %w", err) } - return memInfo{TotalMemory: memStatus.TotalPhys, FreeMemory: memStatus.AvailPhys}, nil + return memInfo{TotalMemory: memStatus.TotalPhys, FreeMemory: memStatus.AvailPhys, FreeSwap: memStatus.AvailPageFile}, nil } diff --git a/gpu/types.go b/gpu/types.go index 7a7749b8e..8d22b06bf 100644 --- a/gpu/types.go +++ b/gpu/types.go @@ -10,6 +10,7 @@ import ( type memInfo struct { TotalMemory uint64 `json:"total_memory,omitempty"` FreeMemory uint64 `json:"free_memory,omitempty"` + FreeSwap uint64 `json:"free_swap,omitempty"` } // Beginning of an `ollama info` command diff --git a/llm/server.go b/llm/server.go index 07c58cfff..8f37aa23a 100644 --- a/llm/server.go +++ b/llm/server.go @@ -88,6 +88,7 @@ func NewLlamaServer(gpus gpu.GpuInfoList, model string, ggml *GGML, adapters, pr var estimate MemoryEstimate var systemTotalMemory uint64 var systemFreeMemory uint64 + var systemSwapFreeMemory uint64 systemMemInfo, err := gpu.GetCPUMem() if err != nil { @@ -95,7 +96,8 @@ func NewLlamaServer(gpus gpu.GpuInfoList, model string, ggml *GGML, adapters, pr } else { systemTotalMemory = systemMemInfo.TotalMemory systemFreeMemory = systemMemInfo.FreeMemory - slog.Debug("system memory", "total", format.HumanBytes2(systemTotalMemory), "free", systemFreeMemory) + systemSwapFreeMemory = systemMemInfo.FreeSwap + slog.Debug("system memory", "total", format.HumanBytes2(systemTotalMemory), "free", format.HumanBytes2(systemFreeMemory), "free_swap", format.HumanBytes2(systemSwapFreeMemory)) } // If the user wants zero GPU layers, reset the gpu list to be CPU/system ram info @@ -125,9 +127,10 @@ func NewLlamaServer(gpus gpu.GpuInfoList, model string, ggml *GGML, adapters, pr // On linux, over-allocating CPU memory will almost always result in an error if runtime.GOOS == "linux" { systemMemoryRequired := estimate.TotalSize - estimate.VRAMSize - if systemMemoryRequired > systemTotalMemory { - slog.Warn("model request too large for system", "requested", format.HumanBytes2(systemMemoryRequired), "system", format.HumanBytes2(systemTotalMemory)) - return nil, fmt.Errorf("model requires more system memory (%s) than is available (%s)", format.HumanBytes2(systemMemoryRequired), format.HumanBytes2(systemTotalMemory)) + available := min(systemTotalMemory, systemFreeMemory+systemSwapFreeMemory) + if systemMemoryRequired > available { + slog.Warn("model request too large for system", "requested", format.HumanBytes2(systemMemoryRequired), "available", available, "total", format.HumanBytes2(systemTotalMemory), "free", format.HumanBytes2(systemFreeMemory), "swap", format.HumanBytes2(systemSwapFreeMemory)) + return nil, fmt.Errorf("model requires more system memory (%s) than is available (%s)", format.HumanBytes2(systemMemoryRequired), format.HumanBytes2(available)) } } From 5056bb9c010f06316b0ff280b879b9c36a7c995c Mon Sep 17 00:00:00 2001 From: Michael Yang Date: Thu, 11 Jul 2024 16:06:57 -0700 Subject: [PATCH 071/384] rename aggregate to contents --- template/template.go | 11 ++++++----- template/template_test.go | 37 +++++++++++++++++++++++++++++++++++-- 2 files changed, 41 insertions(+), 7 deletions(-) diff --git a/template/template.go b/template/template.go index 8d5ac51b8..21e1614d0 100644 --- a/template/template.go +++ b/template/template.go @@ -103,15 +103,16 @@ var response = parse.ActionNode{ } var funcs = template.FuncMap{ - "aggregate": func(v []*api.Message, role string) string { - var aggregated []string + // contents returns the contents of messages with an optional role filter + "contents": func(v []*api.Message, role ...string) string { + var parts []string for _, m := range v { - if m.Role == role { - aggregated = append(aggregated, m.Content) + if len(role) == 0 || role[0] == "" || m.Role == role[0] { + parts = append(parts, m.Content) } } - return strings.Join(aggregated, "\n\n") + return strings.Join(parts, "\n\n") }, } diff --git a/template/template_test.go b/template/template_test.go index 9cfa0beaa..5e5f42570 100644 --- a/template/template_test.go +++ b/template/template_test.go @@ -216,7 +216,7 @@ func TestExecuteWithMessages(t *testing.T) { {"response", `[INST] {{ if .System }}{{ .System }} {{ end }}{{ .Prompt }}[/INST] {{ .Response }}`}, - {"messages", `{{- $system := aggregate $.Messages "system" -}} + {"messages", `{{- $system := contents .Messages "system" -}} {{- range $index, $_ := .Messages }} {{- if eq .Role "user" }}[INST] {{ if $system }}{{ $system }} {{- $system = "" }} @@ -243,7 +243,7 @@ func TestExecuteWithMessages(t *testing.T) { {"response", `[INST] {{ if .System }}{{ .System }} {{ end }}{{ .Prompt }}[/INST] {{ .Response }}`}, - {"messages", `{{- $system := aggregate $.Messages "system" -}} + {"messages", `{{- $system := contents .Messages "system" -}} {{- range $index, $_ := .Messages }} {{- if eq .Role "user" }}[INST] {{ if $system }}{{ $system }} {{- $system = "" }} @@ -363,3 +363,36 @@ Answer: `, }) } } + +func TestFuncs(t *testing.T) { + t.Run("contents", func(t *testing.T) { + cases := map[string]string{ + "": "A\n\nB\n\nC\n\nD\n\nE\n\nF", + "system": "A\n\nF", + "user": "B\n\nE", + "assistant": "C\n\nD", + } + + s := []*api.Message{ + {Role: "system", Content: "A"}, + {Role: "user", Content: "B"}, + {Role: "assistant", Content: "C"}, + {Role: "assistant", Content: "D"}, + {Role: "user", Content: "E"}, + {Role: "system", Content: "F"}, + } + + fn, ok := funcs["contents"].(func([]*api.Message, ...string) string) + if !ok { + t.Fatal("contents is not a function") + } + + for k, v := range cases { + t.Run(k, func(t *testing.T) { + if diff := cmp.Diff(fn(s, k), v); diff != "" { + t.Errorf("mismatch (-got +want):\n%s", diff) + } + }) + } + }) +} From 10e768826c7d5a8f7d7fab13832299a466a01f87 Mon Sep 17 00:00:00 2001 From: Josh <76125168+joshyan1@users.noreply.github.com> Date: Thu, 11 Jul 2024 17:24:29 -0700 Subject: [PATCH 072/384] fix: quant err message (#5616) --- llm/llm.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/llm/llm.go b/llm/llm.go index f2a5e557a..d24507cce 100644 --- a/llm/llm.go +++ b/llm/llm.go @@ -33,7 +33,7 @@ func Quantize(infile, outfile string, ftype fileType) error { params.ftype = ftype.Value() if rc := C.llama_model_quantize(cinfile, coutfile, ¶ms); rc != 0 { - return fmt.Errorf("llama_model_quantize: %d", rc) + return fmt.Errorf("failed to quantize model. This model architecture may not be supported, or you may need to upgrade Ollama to the latest version") } return nil From 179737feb7311fc57c507a93378a3ac15da3a346 Mon Sep 17 00:00:00 2001 From: Jeffrey Morgan Date: Thu, 11 Jul 2024 22:53:46 -0700 Subject: [PATCH 073/384] Clean up old files when installing on Windows (#5645) * app: always clean up install dir; force close applications * remove wildcard * revert `CloseApplications` * whitespace * update `LOCALAPPDATA` var --- app/ollama.iss | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/ollama.iss b/app/ollama.iss index e6502abd3..fef4a7b25 100644 --- a/app/ollama.iss +++ b/app/ollama.iss @@ -127,6 +127,9 @@ Type: filesandordirs; Name: "{%USERPROFILE}\.ollama\models" Type: filesandordirs; Name: "{%USERPROFILE}\.ollama\history" ; NOTE: if the user has a custom OLLAMA_MODELS it will be preserved +[InstallDelete] +Type: filesandordirs; Name: "{%LOCALAPPDATA}\Programs\Ollama" + [Messages] WizardReady=Ollama Windows Preview ReadyLabel1=%nLet's get you up and running with your own large language models. From 36c87c433b7d880ef8b3a2b05ef93b0cd1675520 Mon Sep 17 00:00:00 2001 From: Michael Yang Date: Fri, 12 Jul 2024 11:48:06 -0700 Subject: [PATCH 074/384] template: preprocess message and collect system --- template/template.go | 37 +++++++++++---------------- template/template_test.go | 53 ++++++--------------------------------- 2 files changed, 23 insertions(+), 67 deletions(-) diff --git a/template/template.go b/template/template.go index 21e1614d0..9b3516665 100644 --- a/template/template.go +++ b/template/template.go @@ -102,22 +102,8 @@ var response = parse.ActionNode{ }, } -var funcs = template.FuncMap{ - // contents returns the contents of messages with an optional role filter - "contents": func(v []*api.Message, role ...string) string { - var parts []string - for _, m := range v { - if len(role) == 0 || role[0] == "" || m.Role == role[0] { - parts = append(parts, m.Content) - } - } - - return strings.Join(parts, "\n\n") - }, -} - func Parse(s string) (*Template, error) { - tmpl := template.New("").Option("missingkey=zero").Funcs(funcs) + tmpl := template.New("").Option("missingkey=zero") tmpl, err := tmpl.Parse(s) if err != nil { @@ -163,15 +149,16 @@ type Values struct { } func (t *Template) Execute(w io.Writer, v Values) error { - collated := collate(v.Messages) + system, collated := collate(v.Messages) if !v.forceLegacy && slices.Contains(t.Vars(), "messages") { return t.Template.Execute(w, map[string]any{ + "System": system, "Messages": collated, }) } var b bytes.Buffer - var system, prompt, response string + var prompt, response string for i, m := range collated { switch m.Role { case "system": @@ -223,11 +210,13 @@ func (t *Template) Execute(w io.Writer, v Values) error { } // collate messages based on role. consecutive messages of the same role are merged -// into a single message. collate also pulls out and merges messages with Role == "system" -// which are templated separately. As a side effect, it mangles message content adding image -// tags ([img-%d]) as needed -func collate(msgs []api.Message) (collated []*api.Message) { +// into a single message. collate also collects and returns all system messages. +// collate mutates message content adding image tags ([img-%d]) as needed +func collate(msgs []api.Message) (string, []*api.Message) { var n int + + var system []string + var collated []*api.Message for i := range msgs { msg := msgs[i] for range msg.Images { @@ -240,6 +229,10 @@ func collate(msgs []api.Message) (collated []*api.Message) { n++ } + if msg.Role == "system" { + system = append(system, msg.Content) + } + if len(collated) > 0 && collated[len(collated)-1].Role == msg.Role { collated[len(collated)-1].Content += "\n\n" + msg.Content } else { @@ -247,7 +240,7 @@ func collate(msgs []api.Message) (collated []*api.Message) { } } - return + return strings.Join(system, "\n\n"), collated } func parseNode(n parse.Node) []string { diff --git a/template/template_test.go b/template/template_test.go index 5e5f42570..c678f1b12 100644 --- a/template/template_test.go +++ b/template/template_test.go @@ -216,13 +216,11 @@ func TestExecuteWithMessages(t *testing.T) { {"response", `[INST] {{ if .System }}{{ .System }} {{ end }}{{ .Prompt }}[/INST] {{ .Response }}`}, - {"messages", `{{- $system := contents .Messages "system" -}} -{{- range $index, $_ := .Messages }} -{{- if eq .Role "user" }}[INST] {{ if $system }}{{ $system }} -{{- $system = "" }} + {"messages", `[INST] {{ if .System }}{{ .System }} -{{ end }}{{ .Content }}[/INST] {{ else if eq .Role "assistant" }}{{ .Content }} -{{- end }} +{{ end }} +{{- range .Messages }} +{{- if eq .Role "user" }}{{ .Content }}[/INST] {{ else if eq .Role "assistant" }}{{ .Content }}[INST] {{ end }} {{- end }}`}, }, Values{ @@ -243,13 +241,11 @@ func TestExecuteWithMessages(t *testing.T) { {"response", `[INST] {{ if .System }}{{ .System }} {{ end }}{{ .Prompt }}[/INST] {{ .Response }}`}, - {"messages", `{{- $system := contents .Messages "system" -}} -{{- range $index, $_ := .Messages }} -{{- if eq .Role "user" }}[INST] {{ if $system }}{{ $system }} -{{- $system = "" }} + {"messages", `[INST] {{ if .System }}{{ .System }} -{{ end }}{{ .Content }}[/INST] {{ else if eq .Role "assistant" }}{{ .Content }} -{{- end }} +{{ end }} +{{- range .Messages }} +{{- if eq .Role "user" }}{{ .Content }}[/INST] {{ else if eq .Role "assistant" }}{{ .Content }}[INST] {{ end }} {{- end }}`}, }, Values{ @@ -363,36 +359,3 @@ Answer: `, }) } } - -func TestFuncs(t *testing.T) { - t.Run("contents", func(t *testing.T) { - cases := map[string]string{ - "": "A\n\nB\n\nC\n\nD\n\nE\n\nF", - "system": "A\n\nF", - "user": "B\n\nE", - "assistant": "C\n\nD", - } - - s := []*api.Message{ - {Role: "system", Content: "A"}, - {Role: "user", Content: "B"}, - {Role: "assistant", Content: "C"}, - {Role: "assistant", Content: "D"}, - {Role: "user", Content: "E"}, - {Role: "system", Content: "F"}, - } - - fn, ok := funcs["contents"].(func([]*api.Message, ...string) string) - if !ok { - t.Fatal("contents is not a function") - } - - for k, v := range cases { - t.Run(k, func(t *testing.T) { - if diff := cmp.Diff(fn(s, k), v); diff != "" { - t.Errorf("mismatch (-got +want):\n%s", diff) - } - }) - } - }) -} From 33627331a370755ff5033c0fcd71d1c9210c9d96 Mon Sep 17 00:00:00 2001 From: Jeffrey Morgan Date: Fri, 12 Jul 2024 12:29:23 -0700 Subject: [PATCH 075/384] app: also clean up tempdir runners on install (#5646) --- app/ollama.iss | 1 + 1 file changed, 1 insertion(+) diff --git a/app/ollama.iss b/app/ollama.iss index fef4a7b25..6bedb9ff7 100644 --- a/app/ollama.iss +++ b/app/ollama.iss @@ -128,6 +128,7 @@ Type: filesandordirs; Name: "{%USERPROFILE}\.ollama\history" ; NOTE: if the user has a custom OLLAMA_MODELS it will be preserved [InstallDelete] +Type: filesandordirs; Name: "{%TEMP}\ollama*" Type: filesandordirs; Name: "{%LOCALAPPDATA}\Programs\Ollama" [Messages] From 9ac0a7a50b8d7a0f0627b037c7632181bfbcca97 Mon Sep 17 00:00:00 2001 From: Patrick Devine Date: Fri, 12 Jul 2024 15:41:31 -0700 Subject: [PATCH 076/384] remove template from tests --- cmd/interactive_test.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/cmd/interactive_test.go b/cmd/interactive_test.go index d9af01eb8..711f38604 100644 --- a/cmd/interactive_test.go +++ b/cmd/interactive_test.go @@ -59,7 +59,6 @@ func TestModelfileBuilder(t *testing.T) { opts := runOptions{ Model: "hork", System: "You are part horse and part shark, but all hork. Do horklike things", - Template: "This is a template.", Messages: []api.Message{ {Role: "user", Content: "Hey there hork!"}, {Role: "assistant", Content: "Yes it is true, I am half horse, half shark."}, @@ -75,7 +74,6 @@ func TestModelfileBuilder(t *testing.T) { mf := buildModelfile(opts) expectedModelfile := `FROM {{.Model}} SYSTEM """{{.System}}""" -TEMPLATE """{{.Template}}""" PARAMETER penalize_newline false PARAMETER seed 42 PARAMETER stop [hi there] @@ -97,7 +95,6 @@ MESSAGE assistant """Yes it is true, I am half horse, half shark.""" mf = buildModelfile(opts) expectedModelfile = `FROM {{.ParentModel}} SYSTEM """{{.System}}""" -TEMPLATE """{{.Template}}""" PARAMETER penalize_newline false PARAMETER seed 42 PARAMETER stop [hi there] From 23ebbaa46ead40c44c20b707b0e53d954ea51dc5 Mon Sep 17 00:00:00 2001 From: Patrick Devine Date: Fri, 12 Jul 2024 15:47:17 -0700 Subject: [PATCH 077/384] Revert "remove template from tests" This reverts commit 9ac0a7a50b8d7a0f0627b037c7632181bfbcca97. --- cmd/interactive_test.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/cmd/interactive_test.go b/cmd/interactive_test.go index 711f38604..d9af01eb8 100644 --- a/cmd/interactive_test.go +++ b/cmd/interactive_test.go @@ -59,6 +59,7 @@ func TestModelfileBuilder(t *testing.T) { opts := runOptions{ Model: "hork", System: "You are part horse and part shark, but all hork. Do horklike things", + Template: "This is a template.", Messages: []api.Message{ {Role: "user", Content: "Hey there hork!"}, {Role: "assistant", Content: "Yes it is true, I am half horse, half shark."}, @@ -74,6 +75,7 @@ func TestModelfileBuilder(t *testing.T) { mf := buildModelfile(opts) expectedModelfile := `FROM {{.Model}} SYSTEM """{{.System}}""" +TEMPLATE """{{.Template}}""" PARAMETER penalize_newline false PARAMETER seed 42 PARAMETER stop [hi there] @@ -95,6 +97,7 @@ MESSAGE assistant """Yes it is true, I am half horse, half shark.""" mf = buildModelfile(opts) expectedModelfile = `FROM {{.ParentModel}} SYSTEM """{{.System}}""" +TEMPLATE """{{.Template}}""" PARAMETER penalize_newline false PARAMETER seed 42 PARAMETER stop [hi there] From ebc529cbb3f0b27f6c154fa90e724db8243a7614 Mon Sep 17 00:00:00 2001 From: Michael Yang Date: Fri, 5 Jul 2024 17:31:23 -0700 Subject: [PATCH 078/384] autodetect stop parameters from template --- server/model.go | 21 ++++++++++++++++++--- server/routes_create_test.go | 3 ++- template/alfred.json | 8 ++++++++ template/alpaca.json | 6 ++++++ template/chatml.json | 6 ++++++ template/chatqa.json | 8 ++++++++ template/codellama-70b-instruct.json | 7 +++++++ template/falcon-instruct.json | 6 ++++++ template/gemma-instruct.json | 6 ++++++ template/granite-instruct.json | 7 +++++++ template/llama2-chat.json | 8 ++++++++ template/llama3-instruct.json | 7 +++++++ template/magicoder.json | 6 ++++++ template/mistral-instruct.json | 6 ++++++ template/openchat.json | 5 +++++ template/phi-3.json | 8 ++++++++ template/solar-instruct.json | 7 +++++++ template/starcoder2-instruct.json | 7 +++++++ template/template.go | 14 ++++++++++++++ template/vicuna.json | 6 ++++++ template/zephyr.json | 8 ++++++++ 21 files changed, 156 insertions(+), 4 deletions(-) create mode 100644 template/alfred.json create mode 100644 template/alpaca.json create mode 100644 template/chatml.json create mode 100644 template/chatqa.json create mode 100644 template/codellama-70b-instruct.json create mode 100644 template/falcon-instruct.json create mode 100644 template/gemma-instruct.json create mode 100644 template/granite-instruct.json create mode 100644 template/llama2-chat.json create mode 100644 template/llama3-instruct.json create mode 100644 template/magicoder.json create mode 100644 template/mistral-instruct.json create mode 100644 template/openchat.json create mode 100644 template/phi-3.json create mode 100644 template/solar-instruct.json create mode 100644 template/starcoder2-instruct.json create mode 100644 template/vicuna.json create mode 100644 template/zephyr.json diff --git a/server/model.go b/server/model.go index a79f549a3..d33ffaecc 100644 --- a/server/model.go +++ b/server/model.go @@ -4,6 +4,7 @@ import ( "archive/zip" "bytes" "context" + "encoding/json" "errors" "fmt" "io" @@ -259,13 +260,27 @@ func detectChatTemplate(layers []*layerGGML) ([]*layerGGML, error) { if t, err := template.Named(s); err != nil { slog.Debug("template detection", "error", err) } else { - tmpl, err := NewLayer(t.Reader(), "application/vnd.ollama.image.template") + layer, err := NewLayer(t.Reader(), "application/vnd.ollama.image.template") if err != nil { return nil, err } - tmpl.status = fmt.Sprintf("using autodetected template %s", t.Name) - layers = append(layers, &layerGGML{tmpl, nil}) + layer.status = fmt.Sprintf("using autodetected template %s", t.Name) + layers = append(layers, &layerGGML{layer, nil}) + + if t.Parameters != nil { + var b bytes.Buffer + if err := json.NewEncoder(&b).Encode(t.Parameters); err != nil { + return nil, err + } + + layer, err := NewLayer(&b, "application/vnd.ollama.image.params") + if err != nil { + return nil, err + } + + layers = append(layers, &layerGGML{layer, nil}) + } } } } diff --git a/server/routes_create_test.go b/server/routes_create_test.go index 04174b92e..846720879 100644 --- a/server/routes_create_test.go +++ b/server/routes_create_test.go @@ -545,9 +545,10 @@ func TestCreateDetectTemplate(t *testing.T) { } checkFileExists(t, filepath.Join(p, "blobs", "*"), []string{ + filepath.Join(p, "blobs", "sha256-0d79f567714c62c048378f2107fb332dabee0135d080c302d884317da9433cc5"), filepath.Join(p, "blobs", "sha256-553c4a3f747b3d22a4946875f1cc8ed011c2930d83f864a0c7265f9ec0a20413"), filepath.Join(p, "blobs", "sha256-c608dc615584cd20d9d830363dabf8a4783ae5d34245c3d8c115edb3bc7b28e4"), - filepath.Join(p, "blobs", "sha256-f836ee110db21567f826332e4cedd746c06d10664fd5a9ea3659e3683a944510"), + filepath.Join(p, "blobs", "sha256-ea34c57ba5b78b740aafe2aeb74dc6507fc3ad14170b64c26a04fb9e36c88d75"), }) }) diff --git a/template/alfred.json b/template/alfred.json new file mode 100644 index 000000000..edac21afe --- /dev/null +++ b/template/alfred.json @@ -0,0 +1,8 @@ +{ + "stop": [ + "", + "", + "", + "" + ] +} diff --git a/template/alpaca.json b/template/alpaca.json new file mode 100644 index 000000000..eafe2b8ae --- /dev/null +++ b/template/alpaca.json @@ -0,0 +1,6 @@ +{ + "stop": [ + "### Instruction:", + "### Response" + ] +} diff --git a/template/chatml.json b/template/chatml.json new file mode 100644 index 000000000..7afeb3de5 --- /dev/null +++ b/template/chatml.json @@ -0,0 +1,6 @@ +{ + "stop": [ + "<|im_start|>", + "<|im_end|>" + ] +} diff --git a/template/chatqa.json b/template/chatqa.json new file mode 100644 index 000000000..64dd0f337 --- /dev/null +++ b/template/chatqa.json @@ -0,0 +1,8 @@ +{ + "stop": [ + "System:", + "User:", + "Assistant:", + "<|begin_of_text|>" + ] +} diff --git a/template/codellama-70b-instruct.json b/template/codellama-70b-instruct.json new file mode 100644 index 000000000..a56a63f16 --- /dev/null +++ b/template/codellama-70b-instruct.json @@ -0,0 +1,7 @@ +{ + "stop": [ + "Source:", + "Destination:", + "" + ] +} diff --git a/template/falcon-instruct.json b/template/falcon-instruct.json new file mode 100644 index 000000000..a0da0e813 --- /dev/null +++ b/template/falcon-instruct.json @@ -0,0 +1,6 @@ +{ + "stop": [ + "User:", + "Assistant:" + ] +} diff --git a/template/gemma-instruct.json b/template/gemma-instruct.json new file mode 100644 index 000000000..f4ad415ca --- /dev/null +++ b/template/gemma-instruct.json @@ -0,0 +1,6 @@ +{ + "stop": [ + "", + "" + ] +} diff --git a/template/granite-instruct.json b/template/granite-instruct.json new file mode 100644 index 000000000..0933e4b52 --- /dev/null +++ b/template/granite-instruct.json @@ -0,0 +1,7 @@ +{ + "stop": [ + "System:", + "Question:", + "Answer:" + ] +} diff --git a/template/llama2-chat.json b/template/llama2-chat.json new file mode 100644 index 000000000..17590ab4e --- /dev/null +++ b/template/llama2-chat.json @@ -0,0 +1,8 @@ +{ + "stop": [ + "[INST]", + "[/INST]", + "<>", + "<>" + ] +} diff --git a/template/llama3-instruct.json b/template/llama3-instruct.json new file mode 100644 index 000000000..c4e9d4483 --- /dev/null +++ b/template/llama3-instruct.json @@ -0,0 +1,7 @@ +{ + "stop": [ + "<|start_header_id|>", + "<|end_header_id|>", + "<|eot_id|>" + ] +} diff --git a/template/magicoder.json b/template/magicoder.json new file mode 100644 index 000000000..6f67cab0f --- /dev/null +++ b/template/magicoder.json @@ -0,0 +1,6 @@ +{ + "stop": [ + "@@ Instruction", + "@@ Response" + ] +} diff --git a/template/mistral-instruct.json b/template/mistral-instruct.json new file mode 100644 index 000000000..7afeb3de5 --- /dev/null +++ b/template/mistral-instruct.json @@ -0,0 +1,6 @@ +{ + "stop": [ + "<|im_start|>", + "<|im_end|>" + ] +} diff --git a/template/openchat.json b/template/openchat.json new file mode 100644 index 000000000..0edc341fd --- /dev/null +++ b/template/openchat.json @@ -0,0 +1,5 @@ +{ + "stop": [ + "<|end_of_turn|>" + ] +} diff --git a/template/phi-3.json b/template/phi-3.json new file mode 100644 index 000000000..27bf7664b --- /dev/null +++ b/template/phi-3.json @@ -0,0 +1,8 @@ +{ + "stop": [ + "<|end|>", + "<|system|>", + "<|user|>", + "<|assistant|>" + ] +} diff --git a/template/solar-instruct.json b/template/solar-instruct.json new file mode 100644 index 000000000..7b7a90504 --- /dev/null +++ b/template/solar-instruct.json @@ -0,0 +1,7 @@ +{ + "stop": [ + "### System:", + "### User:", + "### Assistant" + ] +} diff --git a/template/starcoder2-instruct.json b/template/starcoder2-instruct.json new file mode 100644 index 000000000..313489086 --- /dev/null +++ b/template/starcoder2-instruct.json @@ -0,0 +1,7 @@ +{ + "stop": [ + "### Instruction", + "### Response", + "<|endoftext|>" + ] +} diff --git a/template/template.go b/template/template.go index 9b3516665..9bb6a399c 100644 --- a/template/template.go +++ b/template/template.go @@ -23,6 +23,7 @@ import ( var indexBytes []byte //go:embed *.gotmpl +//go:embed *.json var templatesFS embed.FS var templatesOnce = sync.OnceValues(func() ([]*named, error) { @@ -39,6 +40,15 @@ var templatesOnce = sync.OnceValues(func() ([]*named, error) { // normalize line endings t.Bytes = bytes.ReplaceAll(bts, []byte("\r\n"), []byte("\n")) + + params, err := templatesFS.ReadFile(t.Name + ".json") + if err != nil { + continue + } + + if err := json.Unmarshal(params, &t.Parameters); err != nil { + return nil, err + } } return templates, nil @@ -48,6 +58,10 @@ type named struct { Name string `json:"name"` Template string `json:"template"` Bytes []byte + + Parameters *struct { + Stop []string `json:"stop"` + } } func (t named) Reader() io.Reader { diff --git a/template/vicuna.json b/template/vicuna.json new file mode 100644 index 000000000..ed7bfb0fa --- /dev/null +++ b/template/vicuna.json @@ -0,0 +1,6 @@ +{ + "stop": [ + "USER:", + "ASSISTANT:" + ] +} diff --git a/template/zephyr.json b/template/zephyr.json new file mode 100644 index 000000000..f9c0115cc --- /dev/null +++ b/template/zephyr.json @@ -0,0 +1,8 @@ +{ + "stop": [ + "<|system|>", + "", + "<|user|>", + "<|assistant|>" + ] +} From 22c5451fc28b20dd83a389c49d9caf6a1e50a9e3 Mon Sep 17 00:00:00 2001 From: Michael Yang Date: Fri, 12 Jul 2024 21:04:44 -0700 Subject: [PATCH 079/384] fix system prompt (#5662) * fix system prompt * execute template when hitting previous roles * fix tests --------- Co-authored-by: jmorganca --- server/prompt.go | 23 +++++++---------------- server/prompt_test.go | 18 ++++++++++++++++++ template/template.go | 40 ++++++++++++++++++++++++++-------------- 3 files changed, 51 insertions(+), 30 deletions(-) diff --git a/server/prompt.go b/server/prompt.go index 51d691a9f..abc5e61e1 100644 --- a/server/prompt.go +++ b/server/prompt.go @@ -4,7 +4,6 @@ import ( "bytes" "context" "log/slog" - "slices" "github.com/ollama/ollama/api" "github.com/ollama/ollama/llm" @@ -17,26 +16,18 @@ type tokenizeFunc func(context.Context, string) ([]int, error) // chatPrompt truncates any messages that exceed the context window of the model, making sure to always include 1) the // latest message and 2) system messages func chatPrompt(ctx context.Context, m *Model, tokenize tokenizeFunc, opts *api.Options, msgs []api.Message) (prompt string, images []llm.ImageData, _ error) { - // pull out any system messages which should always be included in the prompt var system []api.Message - msgs = slices.DeleteFunc(msgs, func(m api.Message) bool { - if m.Role == "system" { - system = append(system, m) - return true - } - - return false - }) - - if len(system) == 0 && m.System != "" { - // add model system prompt since it wasn't provided - system = append(system, api.Message{Role: "system", Content: m.System}) - } - // always include the last message n := len(msgs) - 1 // in reverse, find all messages that fit into context window for i := n - 1; i >= 0; i-- { + system = make([]api.Message, 0) + for j := range i { + if msgs[j].Role == "system" { + system = append(system, msgs[j]) + } + } + var b bytes.Buffer if err := m.Template.Execute(&b, template.Values{Messages: append(system, msgs[i:]...)}); err != nil { return "", nil, err diff --git a/server/prompt_test.go b/server/prompt_test.go index 1435b143a..d8caf3ed2 100644 --- a/server/prompt_test.go +++ b/server/prompt_test.go @@ -6,6 +6,7 @@ import ( "strings" "testing" + "github.com/google/go-cmp/cmp" "github.com/ollama/ollama/api" "github.com/ollama/ollama/template" ) @@ -164,6 +165,19 @@ func TestChatPrompt(t *testing.T) { prompt: "You are the Test Who Lived. You're a test, Harry! I-I'm a what? A test. And a thumping good one at that, I'd wager. ", }, }, + { + name: "out of order system", + limit: 2048, + msgs: []api.Message{ + {Role: "user", Content: "You're a test, Harry!"}, + {Role: "assistant", Content: "I-I'm a what?"}, + {Role: "system", Content: "You are the Test Who Lived."}, + {Role: "user", Content: "A test. And a thumping good one at that, I'd wager."}, + }, + expect: expect{ + prompt: "You're a test, Harry! I-I'm a what? You are the Test Who Lived. A test. And a thumping good one at that, I'd wager. ", + }, + }, } tmpl, err := template.Parse(` @@ -187,6 +201,10 @@ func TestChatPrompt(t *testing.T) { t.Errorf("expected %q, got %q", tt.prompt, prompt) } + if diff := cmp.Diff(prompt, tt.prompt); diff != "" { + t.Errorf("mismatch (-got +want):\n%s", diff) + } + if len(images) != len(tt.images) { t.Fatalf("expected %d images, got %d", len(tt.images), len(images)) } diff --git a/template/template.go b/template/template.go index 9b3516665..90014ec1a 100644 --- a/template/template.go +++ b/template/template.go @@ -149,27 +149,19 @@ type Values struct { } func (t *Template) Execute(w io.Writer, v Values) error { - system, collated := collate(v.Messages) + system, messages := collate(v.Messages) if !v.forceLegacy && slices.Contains(t.Vars(), "messages") { return t.Template.Execute(w, map[string]any{ "System": system, - "Messages": collated, + "Messages": messages, }) } + system = "" var b bytes.Buffer var prompt, response string - for i, m := range collated { - switch m.Role { - case "system": - system = m.Content - case "user": - prompt = m.Content - case "assistant": - response = m.Content - } - - if i != len(collated)-1 && prompt != "" && response != "" { + for _, m := range messages { + execute := func () error { if err := t.Template.Execute(&b, map[string]any{ "System": system, "Prompt": prompt, @@ -181,6 +173,26 @@ func (t *Template) Execute(w io.Writer, v Values) error { system = "" prompt = "" response = "" + return nil + } + + switch m.Role { + case "system": + if prompt != "" || response != "" { + if err := execute(); err != nil { + return err + } + } + system = m.Content + case "user": + if response != "" { + if err := execute(); err != nil { + return err + } + } + prompt = m.Content + case "assistant": + response = m.Content } } @@ -199,7 +211,7 @@ func (t *Template) Execute(w io.Writer, v Values) error { tree := parse.Tree{Root: nodes.(*parse.ListNode)} if err := template.Must(template.New("").AddParseTree("", &tree)).Execute(&b, map[string]any{ - "System": "", + "System": system, "Prompt": prompt, }); err != nil { return err From 02fea420e5a0042d5e4cfbb5024a6d7e092dc789 Mon Sep 17 00:00:00 2001 From: Jarek Date: Sat, 13 Jul 2024 17:33:46 +0200 Subject: [PATCH 080/384] Add Kerlig AI, an app for macOS (#5675) --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 62f5cd65c..eb5e85329 100644 --- a/README.md +++ b/README.md @@ -293,6 +293,7 @@ See the [API documentation](./docs/api.md) for all endpoints. - [OllamaSpring](https://github.com/CrazyNeil/OllamaSpring) (Ollama Client for macOS) - [LLocal.in](https://github.com/kartikm7/llocal) (Easy to use Electron Desktop Client for Ollama) - [Ollama with Google Mesop](https://github.com/rapidarchitect/ollama_mesop/) (Mesop Chat Client implementation with Ollama) +- [Kerlig AI](https://www.kerlig.com/) (AI writing assistant for macOS) ### Terminal From ef98803d63a4e4c56853688343f011256ced130d Mon Sep 17 00:00:00 2001 From: Jeffrey Morgan Date: Sat, 13 Jul 2024 09:20:05 -0700 Subject: [PATCH 081/384] llm: looser checks for minimum memory (#5677) --- llm/server.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/llm/server.go b/llm/server.go index 8f37aa23a..ffed9fc02 100644 --- a/llm/server.go +++ b/llm/server.go @@ -127,7 +127,7 @@ func NewLlamaServer(gpus gpu.GpuInfoList, model string, ggml *GGML, adapters, pr // On linux, over-allocating CPU memory will almost always result in an error if runtime.GOOS == "linux" { systemMemoryRequired := estimate.TotalSize - estimate.VRAMSize - available := min(systemTotalMemory, systemFreeMemory+systemSwapFreeMemory) + available := systemFreeMemory + systemSwapFreeMemory if systemMemoryRequired > available { slog.Warn("model request too large for system", "requested", format.HumanBytes2(systemMemoryRequired), "available", available, "total", format.HumanBytes2(systemTotalMemory), "free", format.HumanBytes2(systemFreeMemory), "swap", format.HumanBytes2(systemSwapFreeMemory)) return nil, fmt.Errorf("model requires more system memory (%s) than is available (%s)", format.HumanBytes2(systemMemoryRequired), format.HumanBytes2(available)) From 1ed0aa8feab58a5cbdf2d79fdb718e3a5cc03525 Mon Sep 17 00:00:00 2001 From: Jeffrey Morgan Date: Sat, 13 Jul 2024 09:25:31 -0700 Subject: [PATCH 082/384] server: fix `context`, `load_duration` and `total_duration` fields (#5676) * server: fix `contet`, `load_duration` and `total_duration` fields * Update server/routes.go --- server/routes.go | 56 +++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 46 insertions(+), 10 deletions(-) diff --git a/server/routes.go b/server/routes.go index 4059c7c52..5b6d09788 100644 --- a/server/routes.go +++ b/server/routes.go @@ -102,6 +102,7 @@ func (s *Server) scheduleRunner(ctx context.Context, name string, caps []Capabil } func (s *Server) GenerateHandler(c *gin.Context) { + checkpointStart := time.Now() var req api.GenerateRequest if err := c.ShouldBindJSON(&req); errors.Is(err, io.EOF) { c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "missing request body"}) @@ -129,6 +130,8 @@ func (s *Server) GenerateHandler(c *gin.Context) { return } + checkpointLoaded := time.Now() + if req.Prompt == "" { c.JSON(http.StatusOK, api.GenerateResponse{ Model: req.Model, @@ -191,26 +194,48 @@ func (s *Server) GenerateHandler(c *gin.Context) { ch := make(chan any) go func() { + // TODO (jmorganca): avoid building the response twice both here and below + var sb strings.Builder defer close(ch) if err := r.Completion(c.Request.Context(), llm.CompletionRequest{ Prompt: prompt, Images: images, Format: req.Format, Options: opts, - }, func(r llm.CompletionResponse) { - ch <- api.GenerateResponse{ + }, func(cr llm.CompletionResponse) { + res := api.GenerateResponse{ Model: req.Model, CreatedAt: time.Now().UTC(), - Response: r.Content, - Done: r.Done, - DoneReason: r.DoneReason, + Response: cr.Content, + Done: cr.Done, + DoneReason: cr.DoneReason, Metrics: api.Metrics{ - PromptEvalCount: r.PromptEvalCount, - PromptEvalDuration: r.PromptEvalDuration, - EvalCount: r.EvalCount, - EvalDuration: r.EvalDuration, + PromptEvalCount: cr.PromptEvalCount, + PromptEvalDuration: cr.PromptEvalDuration, + EvalCount: cr.EvalCount, + EvalDuration: cr.EvalDuration, }, } + + if _, err := sb.WriteString(cr.Content); err != nil { + ch <- gin.H{"error": err.Error()} + } + + if cr.Done { + res.TotalDuration = time.Since(checkpointStart) + res.LoadDuration = checkpointLoaded.Sub(checkpointStart) + + if !req.Raw { + tokens, err := r.Tokenize(c.Request.Context(), prompt+sb.String()) + if err != nil { + ch <- gin.H{"error": err.Error()} + return + } + res.Context = append(req.Context, tokens...) + } + } + + ch <- res }); err != nil { ch <- gin.H{"error": err.Error()} } @@ -1122,6 +1147,8 @@ func (s *Server) ProcessHandler(c *gin.Context) { } func (s *Server) ChatHandler(c *gin.Context) { + checkpointStart := time.Now() + var req api.ChatRequest if err := c.ShouldBindJSON(&req); errors.Is(err, io.EOF) { c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "missing request body"}) @@ -1141,6 +1168,8 @@ func (s *Server) ChatHandler(c *gin.Context) { return } + checkpointLoaded := time.Now() + if len(req.Messages) == 0 { c.JSON(http.StatusOK, api.ChatResponse{ Model: req.Model, @@ -1169,7 +1198,7 @@ func (s *Server) ChatHandler(c *gin.Context) { Format: req.Format, Options: opts, }, func(r llm.CompletionResponse) { - ch <- api.ChatResponse{ + res := api.ChatResponse{ Model: req.Model, CreatedAt: time.Now().UTC(), Message: api.Message{Role: "assistant", Content: r.Content}, @@ -1182,6 +1211,13 @@ func (s *Server) ChatHandler(c *gin.Context) { EvalDuration: r.EvalDuration, }, } + + if r.Done { + res.TotalDuration = time.Since(checkpointStart) + res.LoadDuration = checkpointLoaded.Sub(checkpointStart) + } + + ch <- res }); err != nil { ch <- gin.H{"error": err.Error()} } From f7ee0123008dbdb3fd5954438d12196951b58b78 Mon Sep 17 00:00:00 2001 From: jmorganca Date: Sat, 13 Jul 2024 15:08:00 -0700 Subject: [PATCH 083/384] server: prepend system message in chat handler --- server/routes.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/server/routes.go b/server/routes.go index 5b6d09788..edaec6912 100644 --- a/server/routes.go +++ b/server/routes.go @@ -1181,6 +1181,10 @@ func (s *Server) ChatHandler(c *gin.Context) { return } + if req.Messages[0].Role != "system" { + req.Messages = append([]api.Message{{Role: "system", Content: m.System}}, req.Messages...) + } + prompt, images, err := chatPrompt(c.Request.Context(), m, r.Tokenize, opts, req.Messages) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) From 057d31861e3514b60a7eedf694899067b72bd2fa Mon Sep 17 00:00:00 2001 From: Patrick Devine Date: Sat, 13 Jul 2024 20:56:24 -0700 Subject: [PATCH 084/384] remove template (#5655) --- api/types.go | 2 ++ cmd/cmd.go | 2 -- cmd/interactive.go | 52 +++++++++++------------------------------ cmd/interactive_test.go | 3 --- server/routes.go | 7 ------ 5 files changed, 16 insertions(+), 50 deletions(-) diff --git a/api/types.go b/api/types.go index 87844c67c..91c97c715 100644 --- a/api/types.go +++ b/api/types.go @@ -221,6 +221,8 @@ type DeleteRequest struct { type ShowRequest struct { Model string `json:"model"` System string `json:"system"` + + // Template is deprecated Template string `json:"template"` Verbose bool `json:"verbose"` diff --git a/cmd/cmd.go b/cmd/cmd.go index c898c7db6..2252a905e 100644 --- a/cmd/cmd.go +++ b/cmd/cmd.go @@ -843,7 +843,6 @@ type runOptions struct { WordWrap bool Format string System string - Template string Images []api.ImageData Options map[string]interface{} MultiModal bool @@ -1037,7 +1036,6 @@ func generate(cmd *cobra.Command, opts runOptions) error { Images: opts.Images, Format: opts.Format, System: opts.System, - Template: opts.Template, Options: opts.Options, KeepAlive: opts.KeepAlive, } diff --git a/cmd/interactive.go b/cmd/interactive.go index 9214f2db5..adbc3e9fb 100644 --- a/cmd/interactive.go +++ b/cmd/interactive.go @@ -27,7 +27,6 @@ const ( MultilineNone MultilineState = iota MultilinePrompt MultilineSystem - MultilineTemplate ) func loadModel(cmd *cobra.Command, opts *runOptions) error { @@ -94,7 +93,6 @@ func generateInteractive(cmd *cobra.Command, opts runOptions) error { fmt.Fprintln(os.Stderr, "Available Commands:") fmt.Fprintln(os.Stderr, " /set parameter ... Set a parameter") fmt.Fprintln(os.Stderr, " /set system Set system message") - fmt.Fprintln(os.Stderr, " /set template Set prompt template") fmt.Fprintln(os.Stderr, " /set history Enable history") fmt.Fprintln(os.Stderr, " /set nohistory Disable history") fmt.Fprintln(os.Stderr, " /set wordwrap Enable wordwrap") @@ -204,10 +202,6 @@ func generateInteractive(cmd *cobra.Command, opts runOptions) error { opts.Messages = append(opts.Messages, api.Message{Role: "system", Content: opts.System}) fmt.Println("Set system message.") sb.Reset() - case MultilineTemplate: - opts.Template = sb.String() - fmt.Println("Set prompt template.") - sb.Reset() } multiline = MultilineNone @@ -326,17 +320,13 @@ func generateInteractive(cmd *cobra.Command, opts runOptions) error { } fmt.Printf("Set parameter '%s' to '%s'\n", args[2], strings.Join(params, ", ")) opts.Options[args[2]] = fp[args[2]] - case "system", "template": + case "system": if len(args) < 3 { usageSet() continue } - if args[1] == "system" { - multiline = MultilineSystem - } else if args[1] == "template" { - multiline = MultilineTemplate - } + multiline = MultilineSystem line := strings.Join(args[2:], " ") line, ok := strings.CutPrefix(line, `"""`) @@ -356,23 +346,17 @@ func generateInteractive(cmd *cobra.Command, opts runOptions) error { continue } - if args[1] == "system" { - opts.System = sb.String() // for display in modelfile - newMessage := api.Message{Role: "system", Content: sb.String()} - // Check if the slice is not empty and the last message is from 'system' - if len(opts.Messages) > 0 && opts.Messages[len(opts.Messages)-1].Role == "system" { - // Replace the last message - opts.Messages[len(opts.Messages)-1] = newMessage - } else { - opts.Messages = append(opts.Messages, newMessage) - } - fmt.Println("Set system message.") - sb.Reset() - } else if args[1] == "template" { - opts.Template = sb.String() - fmt.Println("Set prompt template.") - sb.Reset() + opts.System = sb.String() // for display in modelfile + newMessage := api.Message{Role: "system", Content: sb.String()} + // Check if the slice is not empty and the last message is from 'system' + if len(opts.Messages) > 0 && opts.Messages[len(opts.Messages)-1].Role == "system" { + // Replace the last message + opts.Messages[len(opts.Messages)-1] = newMessage + } else { + opts.Messages = append(opts.Messages, newMessage) } + fmt.Println("Set system message.") + sb.Reset() sb.Reset() continue @@ -393,7 +377,6 @@ func generateInteractive(cmd *cobra.Command, opts runOptions) error { req := &api.ShowRequest{ Name: opts.Model, System: opts.System, - Template: opts.Template, Options: opts.Options, } resp, err := client.Show(cmd.Context(), req) @@ -437,12 +420,9 @@ func generateInteractive(cmd *cobra.Command, opts runOptions) error { fmt.Println("No system message was specified for this model.") } case "template": - switch { - case opts.Template != "": - fmt.Println(opts.Template + "\n") - case resp.Template != "": + if resp.Template != "" { fmt.Println(resp.Template) - default: + } else { fmt.Println("No prompt template was specified for this model.") } default: @@ -536,10 +516,6 @@ func buildModelfile(opts runOptions) string { fmt.Fprintf(&mf, "SYSTEM \"\"\"%s\"\"\"\n", opts.System) } - if opts.Template != "" { - fmt.Fprintf(&mf, "TEMPLATE \"\"\"%s\"\"\"\n", opts.Template) - } - keys := make([]string, 0) for k := range opts.Options { keys = append(keys, k) diff --git a/cmd/interactive_test.go b/cmd/interactive_test.go index d9af01eb8..711f38604 100644 --- a/cmd/interactive_test.go +++ b/cmd/interactive_test.go @@ -59,7 +59,6 @@ func TestModelfileBuilder(t *testing.T) { opts := runOptions{ Model: "hork", System: "You are part horse and part shark, but all hork. Do horklike things", - Template: "This is a template.", Messages: []api.Message{ {Role: "user", Content: "Hey there hork!"}, {Role: "assistant", Content: "Yes it is true, I am half horse, half shark."}, @@ -75,7 +74,6 @@ func TestModelfileBuilder(t *testing.T) { mf := buildModelfile(opts) expectedModelfile := `FROM {{.Model}} SYSTEM """{{.System}}""" -TEMPLATE """{{.Template}}""" PARAMETER penalize_newline false PARAMETER seed 42 PARAMETER stop [hi there] @@ -97,7 +95,6 @@ MESSAGE assistant """Yes it is true, I am half horse, half shark.""" mf = buildModelfile(opts) expectedModelfile = `FROM {{.ParentModel}} SYSTEM """{{.System}}""" -TEMPLATE """{{.Template}}""" PARAMETER penalize_newline false PARAMETER seed 42 PARAMETER stop [hi there] diff --git a/server/routes.go b/server/routes.go index edaec6912..0a00d9e23 100644 --- a/server/routes.go +++ b/server/routes.go @@ -574,13 +574,6 @@ func GetModelInfo(req api.ShowRequest) (*api.ShowResponse, error) { m.System = req.System } - if req.Template != "" { - m.Template, err = template.Parse(req.Template) - if err != nil { - return nil, err - } - } - msgs := make([]api.Message, len(m.Messages)) for i, msg := range m.Messages { msgs[i] = api.Message{Role: msg.Role, Content: msg.Content} From e9f7f3602961d2b0beaff27144ec89301c2173ca Mon Sep 17 00:00:00 2001 From: royjhan <65097070+royjhan@users.noreply.github.com> Date: Sat, 13 Jul 2024 22:07:45 -0700 Subject: [PATCH 085/384] Support image input for OpenAI chat compatibility (#5208) * OpenAI v1 models * Refactor Writers * Add Test Co-Authored-By: Attila Kerekes * Credit Co-Author Co-Authored-By: Attila Kerekes <439392+keriati@users.noreply.github.com> * Empty List Testing * Use Namespace for Ownedby * Update Test * Add back envconfig * v1/models docs * Use ModelName Parser * Test Names * Remove Docs * Clean Up * Test name Co-authored-by: Jeffrey Morgan * Add Middleware for Chat and List * Testing Cleanup * Test with Fatal * Add functionality to chat test * Support image input for OpenAI chat * Decoding * Fix message processing logic * openai vision test * type errors * clean up * redundant check * merge conflicts * merge conflicts * merge conflicts * flattening and smaller image * add test * support python and js SDKs and mandate prefixing * clean up --------- Co-authored-by: Attila Kerekes <439392+keriati@users.noreply.github.com> Co-authored-by: Jeffrey Morgan --- openai/openai.go | 76 +++++++++++++++++++++++++++++++++++++++---- openai/openai_test.go | 49 ++++++++++++++++++++++++++++ 2 files changed, 119 insertions(+), 6 deletions(-) diff --git a/openai/openai.go b/openai/openai.go index 1707da14b..b289d73e8 100644 --- a/openai/openai.go +++ b/openai/openai.go @@ -3,11 +3,13 @@ package openai import ( "bytes" + "encoding/base64" "encoding/json" "fmt" "io" "math/rand" "net/http" + "strings" "time" "github.com/gin-gonic/gin" @@ -28,7 +30,7 @@ type ErrorResponse struct { type Message struct { Role string `json:"role"` - Content string `json:"content"` + Content any `json:"content"` } type Choice struct { @@ -269,10 +271,66 @@ func toModel(r api.ShowResponse, m string) Model { } } -func fromChatRequest(r ChatCompletionRequest) api.ChatRequest { +func fromChatRequest(r ChatCompletionRequest) (*api.ChatRequest, error) { var messages []api.Message for _, msg := range r.Messages { - messages = append(messages, api.Message{Role: msg.Role, Content: msg.Content}) + switch content := msg.Content.(type) { + case string: + messages = append(messages, api.Message{Role: msg.Role, Content: content}) + case []any: + message := api.Message{Role: msg.Role} + for _, c := range content { + data, ok := c.(map[string]any) + if !ok { + return nil, fmt.Errorf("invalid message format") + } + switch data["type"] { + case "text": + text, ok := data["text"].(string) + if !ok { + return nil, fmt.Errorf("invalid message format") + } + message.Content = text + case "image_url": + var url string + if urlMap, ok := data["image_url"].(map[string]any); ok { + if url, ok = urlMap["url"].(string); !ok { + return nil, fmt.Errorf("invalid message format") + } + } else { + if url, ok = data["image_url"].(string); !ok { + return nil, fmt.Errorf("invalid message format") + } + } + + types := []string{"jpeg", "jpg", "png"} + valid := false + for _, t := range types { + prefix := "data:image/" + t + ";base64," + if strings.HasPrefix(url, prefix) { + url = strings.TrimPrefix(url, prefix) + valid = true + break + } + } + + if !valid { + return nil, fmt.Errorf("invalid image input") + } + + img, err := base64.StdEncoding.DecodeString(url) + if err != nil { + return nil, fmt.Errorf("invalid message format") + } + message.Images = append(message.Images, img) + default: + return nil, fmt.Errorf("invalid message format") + } + } + messages = append(messages, message) + default: + return nil, fmt.Errorf("invalid message content type: %T", content) + } } options := make(map[string]interface{}) @@ -323,13 +381,13 @@ func fromChatRequest(r ChatCompletionRequest) api.ChatRequest { format = "json" } - return api.ChatRequest{ + return &api.ChatRequest{ Model: r.Model, Messages: messages, Format: format, Options: options, Stream: &r.Stream, - } + }, nil } func fromCompleteRequest(r CompletionRequest) (api.GenerateRequest, error) { @@ -656,7 +714,13 @@ func ChatMiddleware() gin.HandlerFunc { } var b bytes.Buffer - if err := json.NewEncoder(&b).Encode(fromChatRequest(req)); err != nil { + + chatReq, err := fromChatRequest(req) + if err != nil { + c.AbortWithStatusJSON(http.StatusBadRequest, NewError(http.StatusBadRequest, err.Error())) + } + + if err := json.NewEncoder(&b).Encode(chatReq); err != nil { c.AbortWithStatusJSON(http.StatusInternalServerError, NewError(http.StatusInternalServerError, err.Error())) return } diff --git a/openai/openai_test.go b/openai/openai_test.go index 5f1ae52e9..99f8baaf3 100644 --- a/openai/openai_test.go +++ b/openai/openai_test.go @@ -2,6 +2,7 @@ package openai import ( "bytes" + "encoding/base64" "encoding/json" "io" "net/http" @@ -15,6 +16,10 @@ import ( "github.com/stretchr/testify/assert" ) +const prefix = `data:image/jpeg;base64,` +const image = `iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk+A8AAQUBAScY42YAAAAASUVORK5CYII=` +const imageURL = prefix + image + func TestMiddlewareRequests(t *testing.T) { type testCase struct { Name string @@ -112,6 +117,50 @@ func TestMiddlewareRequests(t *testing.T) { } }, }, + { + Name: "chat handler with image content", + Method: http.MethodPost, + Path: "/api/chat", + Handler: ChatMiddleware, + Setup: func(t *testing.T, req *http.Request) { + body := ChatCompletionRequest{ + Model: "test-model", + Messages: []Message{ + { + Role: "user", Content: []map[string]any{ + {"type": "text", "text": "Hello"}, + {"type": "image_url", "image_url": map[string]string{"url": imageURL}}, + }, + }, + }, + } + + bodyBytes, _ := json.Marshal(body) + + req.Body = io.NopCloser(bytes.NewReader(bodyBytes)) + req.Header.Set("Content-Type", "application/json") + }, + Expected: func(t *testing.T, req *http.Request) { + var chatReq api.ChatRequest + if err := json.NewDecoder(req.Body).Decode(&chatReq); err != nil { + t.Fatal(err) + } + + if chatReq.Messages[0].Role != "user" { + t.Fatalf("expected 'user', got %s", chatReq.Messages[0].Role) + } + + if chatReq.Messages[0].Content != "Hello" { + t.Fatalf("expected 'Hello', got %s", chatReq.Messages[0].Content) + } + + img, _ := base64.StdEncoding.DecodeString(imageURL[len(prefix):]) + + if !bytes.Equal(chatReq.Messages[0].Images[0], img) { + t.Fatalf("expected image encoding, got %s", chatReq.Messages[0].Images[0]) + } + }, + }, } gin.SetMode(gin.TestMode) From b9f5e16c8025f115abde34ff047023f4d6e34af5 Mon Sep 17 00:00:00 2001 From: royjhan <65097070+royjhan@users.noreply.github.com> Date: Mon, 15 Jul 2024 12:14:24 -0700 Subject: [PATCH 086/384] Introduce `/api/embed` endpoint supporting batch embedding (#5127) * Initial Batch Embedding * Revert "Initial Batch Embedding" This reverts commit c22d54895a280b54c727279d85a5fc94defb5a29. * Initial Draft * mock up notes * api/embed draft * add server function * check normalization * clean up * normalization * playing around with truncate stuff * Truncation * Truncation * move normalization to go * Integration Test Template * Truncation Integration Tests * Clean up * use float32 * move normalize * move normalize test * refactoring * integration float32 * input handling and handler testing * Refactoring of legacy and new * clear comments * merge conflicts * touches * embedding type 64 * merge conflicts * fix hanging on single string * refactoring * test values * set context length * clean up * testing clean up * testing clean up * remove function closure * Revert "remove function closure" This reverts commit 55d48c6ed17abe42e7a122e69d603ef0c1506787. * remove function closure * remove redundant error check * clean up * more clean up * clean up --- api/client.go | 11 ++- api/types.go | 24 ++++++ integration/embed_test.go | 152 ++++++++++++++++++++++++++++++++++++++ llm/ext_server/server.cpp | 37 ++++++---- llm/server.go | 16 ++-- server/routes.go | 131 +++++++++++++++++++++++++++++++- server/routes_test.go | 103 ++++++++++++++++++++++++++ server/sched_test.go | 8 +- 8 files changed, 452 insertions(+), 30 deletions(-) create mode 100644 integration/embed_test.go diff --git a/api/client.go b/api/client.go index fccbc9ad7..c59fbc423 100644 --- a/api/client.go +++ b/api/client.go @@ -347,7 +347,16 @@ func (c *Client) Heartbeat(ctx context.Context) error { return nil } -// Embeddings generates embeddings from a model. +// Embed generates embeddings from a model. +func (c *Client) Embed(ctx context.Context, req *EmbedRequest) (*EmbedResponse, error) { + var resp EmbedResponse + if err := c.do(ctx, http.MethodPost, "/api/embed", req, &resp); err != nil { + return nil, err + } + return &resp, nil +} + +// Embeddings generates an embedding from a model. func (c *Client) Embeddings(ctx context.Context, req *EmbeddingRequest) (*EmbeddingResponse, error) { var resp EmbeddingResponse if err := c.do(ctx, http.MethodPost, "/api/embeddings", req, &resp); err != nil { diff --git a/api/types.go b/api/types.go index 91c97c715..bf5529283 100644 --- a/api/types.go +++ b/api/types.go @@ -173,6 +173,30 @@ type Runner struct { NumThread int `json:"num_thread,omitempty"` } +// EmbedRequest is the request passed to [Client.Embed]. +type EmbedRequest struct { + // Model is the model name. + Model string `json:"model"` + + // Input is the input to embed. + Input any `json:"input"` + + // KeepAlive controls how long the model will stay loaded in memory following + // this request. + KeepAlive *Duration `json:"keep_alive,omitempty"` + + Truncate *bool `json:"truncate,omitempty"` + + // Options lists model-specific options. + Options map[string]interface{} `json:"options"` +} + +// EmbedResponse is the response from [Client.Embed]. +type EmbedResponse struct { + Model string `json:"model"` + Embeddings [][]float32 `json:"embeddings,omitempty"` +} + // EmbeddingRequest is the request passed to [Client.Embeddings]. type EmbeddingRequest struct { // Model is the model name. diff --git a/integration/embed_test.go b/integration/embed_test.go new file mode 100644 index 000000000..aeafa57b6 --- /dev/null +++ b/integration/embed_test.go @@ -0,0 +1,152 @@ +//go:build integration + +package integration + +import ( + "context" + "testing" + "time" + + "github.com/ollama/ollama/api" +) + +func TestAllMiniLMEmbed(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute) + defer cancel() + + req := api.EmbedRequest{ + Model: "all-minilm", + Input: "why is the sky blue?", + } + + res, err := embedTestHelper(ctx, t, req) + + if err != nil { + t.Fatalf("error: %v", err) + } + + if len(res.Embeddings) != 1 { + t.Fatalf("expected 1 embedding, got %d", len(res.Embeddings)) + } + + if len(res.Embeddings[0]) != 384 { + t.Fatalf("expected 384 floats, got %d", len(res.Embeddings[0])) + } + + if res.Embeddings[0][0] != 0.010071031 { + t.Fatalf("expected 0.010071031, got %f", res.Embeddings[0][0]) + } +} + +func TestAllMiniLMBatchEmbed(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute) + defer cancel() + + req := api.EmbedRequest{ + Model: "all-minilm", + Input: []string{"why is the sky blue?", "why is the grass green?"}, + } + + res, err := embedTestHelper(ctx, t, req) + + if err != nil { + t.Fatalf("error: %v", err) + } + + if len(res.Embeddings) != 2 { + t.Fatalf("expected 2 embeddings, got %d", len(res.Embeddings)) + } + + if len(res.Embeddings[0]) != 384 { + t.Fatalf("expected 384 floats, got %d", len(res.Embeddings[0])) + } + + if res.Embeddings[0][0] != 0.010071031 || res.Embeddings[1][0] != -0.009802706 { + t.Fatalf("expected 0.010071031 and -0.009802706, got %f and %f", res.Embeddings[0][0], res.Embeddings[1][0]) + } +} + +func TestAllMiniLmEmbedTruncate(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute) + defer cancel() + + truncTrue, truncFalse := true, false + + type testReq struct { + Name string + Request api.EmbedRequest + } + + reqs := []testReq{ + { + Name: "Target Truncation", + Request: api.EmbedRequest{ + Model: "all-minilm", + Input: "why", + }, + }, + { + Name: "Default Truncate", + Request: api.EmbedRequest{ + Model: "all-minilm", + Input: "why is the sky blue?", + Options: map[string]any{"num_ctx": 1}, + }, + }, + { + Name: "Explicit Truncate", + Request: api.EmbedRequest{ + Model: "all-minilm", + Input: "why is the sky blue?", + Truncate: &truncTrue, + Options: map[string]any{"num_ctx": 1}, + }, + }, + } + + res := make(map[string]*api.EmbedResponse) + + for _, req := range reqs { + response, err := embedTestHelper(ctx, t, req.Request) + if err != nil { + t.Fatalf("error: %v", err) + } + res[req.Name] = response + } + + if res["Target Truncation"].Embeddings[0][0] != res["Default Truncate"].Embeddings[0][0] { + t.Fatal("expected default request to truncate correctly") + } + + if res["Default Truncate"].Embeddings[0][0] != res["Explicit Truncate"].Embeddings[0][0] { + t.Fatal("expected default request and truncate true request to be the same") + } + + // check that truncate set to false returns an error if context length is exceeded + _, err := embedTestHelper(ctx, t, api.EmbedRequest{ + Model: "all-minilm", + Input: "why is the sky blue?", + Truncate: &truncFalse, + Options: map[string]any{"num_ctx": 1}, + }) + + if err == nil { + t.Fatal("expected error, got nil") + } +} + +func embedTestHelper(ctx context.Context, t *testing.T, req api.EmbedRequest) (*api.EmbedResponse, error) { + client, _, cleanup := InitServerConnection(ctx, t) + defer cleanup() + if err := PullIfMissing(ctx, client, req.Model); err != nil { + t.Fatalf("failed to pull model %s: %v", req.Model, err) + } + + response, err := client.Embed(ctx, &req) + + if err != nil { + return nil, err + } + + return response, nil +} diff --git a/llm/ext_server/server.cpp b/llm/ext_server/server.cpp index 0ef3956ec..e8a076c43 100644 --- a/llm/ext_server/server.cpp +++ b/llm/ext_server/server.cpp @@ -3188,26 +3188,33 @@ int main(int argc, char **argv) { prompt = ""; } - json image_data; - if (body.count("image_data") != 0) { - image_data = body["image_data"]; - } - else - { - image_data = ""; + if (prompt.size() == 1) { + prompt = prompt[0]; } // create and queue the task - const int task_id = llama.queue_tasks.get_new_id(); - llama.queue_results.add_waiting_task_id(task_id); - llama.request_completion(task_id, { {"prompt", prompt}, { "n_predict", 0}, {"image_data", image_data} }, true, -1); + json responses; + { + const int id_task = llama.queue_tasks.get_new_id(); + llama.queue_results.add_waiting_task_id(id_task); + llama.request_completion(id_task, {{"prompt", prompt}}, true, -1); - // get the result - task_result result = llama.queue_results.recv(task_id); - llama.queue_results.remove_waiting_task_id(task_id); + // get the result + task_result result = llama.queue_results.recv(id_task); + llama.queue_results.remove_waiting_task_id(id_task); + if (result.error) { + return res.set_content(result.result_json.dump(), "application/json; charset=utf-8"); + } - // send the result - return res.set_content(result.result_json.dump(), "application/json; charset=utf-8"); + responses = result.result_json.value("results", std::vector{result.result_json}); + json embeddings = json::array(); + for (auto & elem : responses) { + embeddings.push_back(elem.at("embedding")); + } + // send the result + json embedding_res = json{{"embedding", embeddings}}; + return res.set_content(embedding_res.dump(), "application/json; charset=utf-8"); + } }); // GG: if I put the main loop inside a thread, it crashes on the first request when build in Debug!? diff --git a/llm/server.go b/llm/server.go index ffed9fc02..36c0e0b55 100644 --- a/llm/server.go +++ b/llm/server.go @@ -33,7 +33,7 @@ type LlamaServer interface { Ping(ctx context.Context) error WaitUntilRunning(ctx context.Context) error Completion(ctx context.Context, req CompletionRequest, fn func(CompletionResponse)) error - Embedding(ctx context.Context, prompt string) ([]float64, error) + Embed(ctx context.Context, input []string) ([][]float32, error) Tokenize(ctx context.Context, content string) ([]int, error) Detokenize(ctx context.Context, tokens []int) (string, error) Close() error @@ -867,15 +867,15 @@ func (s *llmServer) Completion(ctx context.Context, req CompletionRequest, fn fu return nil } -type EmbeddingRequest struct { - Content string `json:"content"` +type EmbedRequest struct { + Content []string `json:"content"` } -type EmbeddingResponse struct { - Embedding []float64 `json:"embedding"` +type EmbedResponse struct { + Embedding [][]float32 `json:"embedding"` } -func (s *llmServer) Embedding(ctx context.Context, prompt string) ([]float64, error) { +func (s *llmServer) Embed(ctx context.Context, input []string) ([][]float32, error) { if err := s.sem.Acquire(ctx, 1); err != nil { slog.Error("Failed to acquire semaphore", "error", err) return nil, err @@ -890,7 +890,7 @@ func (s *llmServer) Embedding(ctx context.Context, prompt string) ([]float64, er return nil, fmt.Errorf("unexpected server status: %s", status.ToString()) } - data, err := json.Marshal(TokenizeRequest{Content: prompt}) + data, err := json.Marshal(EmbedRequest{Content: input}) if err != nil { return nil, fmt.Errorf("error marshaling embed data: %w", err) } @@ -917,7 +917,7 @@ func (s *llmServer) Embedding(ctx context.Context, prompt string) ([]float64, er return nil, fmt.Errorf("%s", body) } - var embedding EmbeddingResponse + var embedding EmbedResponse if err := json.Unmarshal(body, &embedding); err != nil { return nil, fmt.Errorf("unmarshal tokenize response: %w", err) } diff --git a/server/routes.go b/server/routes.go index 0a00d9e23..c5c3a19ca 100644 --- a/server/routes.go +++ b/server/routes.go @@ -9,6 +9,7 @@ import ( "fmt" "io" "log/slog" + "math" "net" "net/http" "net/netip" @@ -271,6 +272,121 @@ func (s *Server) GenerateHandler(c *gin.Context) { streamResponse(c, ch) } +func (s *Server) EmbedHandler(c *gin.Context) { + var req api.EmbedRequest + err := c.ShouldBindJSON(&req) + switch { + case errors.Is(err, io.EOF): + c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "missing request body"}) + return + case err != nil: + c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + truncate := true + + if req.Truncate != nil && !*req.Truncate { + truncate = false + } + + var input []string + + switch i := req.Input.(type) { + case string: + if len(i) > 0 { + input = append(input, i) + } + case []any: + for _, v := range i { + if _, ok := v.(string); !ok { + c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "invalid input type"}) + return + } + input = append(input, v.(string)) + } + default: + c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "invalid input type"}) + return + } + + if len(input) == 0 { + c.JSON(http.StatusOK, api.EmbedResponse{Model: req.Model, Embeddings: [][]float32{}}) + return + } + + r, m, opts, err := s.scheduleRunner(c.Request.Context(), req.Model, []Capability{}, req.Options, req.KeepAlive) + if err != nil { + handleScheduleError(c, req.Model, err) + return + } + + kvData, err := getKVData(m.ModelPath, false) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + + for i, s := range input { + tokens, err := r.Tokenize(c.Request.Context(), s) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + + ctxLen := min(opts.NumCtx, int(kvData.ContextLength())) + if len(tokens) > ctxLen { + if !truncate { + c.JSON(http.StatusBadRequest, gin.H{"error": "input length exceeds maximum context length"}) + return + } + + tokens = tokens[:ctxLen] + s, err = r.Detokenize(c.Request.Context(), tokens) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + } + + input[i] = s + } + embeddings, err := r.Embed(c.Request.Context(), input) + + if err != nil { + slog.Error("embedding generation failed", "error", err) + c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to generate embedding"}) + return + } + + for i, e := range embeddings { + embeddings[i] = normalize(e) + } + + resp := api.EmbedResponse{ + Model: req.Model, + Embeddings: embeddings, + } + c.JSON(http.StatusOK, resp) +} + +func normalize(vec []float32) []float32 { + var sum float32 + for _, v := range vec { + sum += v * v + } + + norm := float32(0.0) + if sum > 0 { + norm = float32(1.0 / math.Sqrt(float64(sum))) + } + + for i := range vec { + vec[i] *= norm + } + return vec +} + func (s *Server) EmbeddingsHandler(c *gin.Context) { var req api.EmbeddingRequest if err := c.ShouldBindJSON(&req); errors.Is(err, io.EOF) { @@ -293,14 +409,24 @@ func (s *Server) EmbeddingsHandler(c *gin.Context) { return } - embedding, err := r.Embedding(c.Request.Context(), req.Prompt) + embeddings, err := r.Embed(c.Request.Context(), []string{req.Prompt}) + if err != nil { slog.Info(fmt.Sprintf("embedding generation failed: %v", err)) c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to generate embedding"}) return } - c.JSON(http.StatusOK, api.EmbeddingResponse{Embedding: embedding}) + embedding := make([]float64, len(embeddings[0])) + + for i, v := range embeddings[0] { + embedding[i] = float64(v) + } + + resp := api.EmbeddingResponse{ + Embedding: embedding, + } + c.JSON(http.StatusOK, resp) } func (s *Server) PullModelHandler(c *gin.Context) { @@ -919,6 +1045,7 @@ func (s *Server) GenerateRoutes() http.Handler { r.POST("/api/pull", s.PullModelHandler) r.POST("/api/generate", s.GenerateHandler) r.POST("/api/chat", s.ChatHandler) + r.POST("/api/embed", s.EmbedHandler) r.POST("/api/embeddings", s.EmbeddingsHandler) r.POST("/api/create", s.CreateModelHandler) r.POST("/api/push", s.PushModelHandler) diff --git a/server/routes_test.go b/server/routes_test.go index 50eaf7e97..70622e9b0 100644 --- a/server/routes_test.go +++ b/server/routes_test.go @@ -7,6 +7,7 @@ import ( "encoding/json" "fmt" "io" + "math" "net/http" "net/http/httptest" "os" @@ -272,6 +273,73 @@ func Test_Routes(t *testing.T) { assert.Equal(t, "library", retrieveResp.OwnedBy) }, }, + { + Name: "Embed Handler Empty Input", + Method: http.MethodPost, + Path: "/api/embed", + Setup: func(t *testing.T, req *http.Request) { + embedReq := api.EmbedRequest{ + Model: "t-bone", + Input: "", + } + jsonData, err := json.Marshal(embedReq) + require.NoError(t, err) + req.Body = io.NopCloser(bytes.NewReader(jsonData)) + }, + Expected: func(t *testing.T, resp *http.Response) { + contentType := resp.Header.Get("Content-Type") + if contentType != "application/json; charset=utf-8" { + t.Fatalf("expected content type application/json; charset=utf-8, got %s", contentType) + } + body, err := io.ReadAll(resp.Body) + if err != nil { + t.Fatal(err) + } + + var embedResp api.EmbedResponse + err = json.Unmarshal(body, &embedResp) + if err != nil { + t.Fatal(err) + } + + if embedResp.Model != "t-bone" { + t.Fatalf("expected model t-bone, got %s", embedResp.Model) + } + + if embedResp.Embeddings != nil { + t.Fatalf("expected embeddings to be nil, got %v", embedResp.Embeddings) + } + }, + }, + { + Name: "Embed Handler Invalid Input", + Method: http.MethodPost, + Path: "/api/embed", + Setup: func(t *testing.T, req *http.Request) { + embedReq := api.EmbedRequest{ + Model: "t-bone", + Input: 2, + } + jsonData, err := json.Marshal(embedReq) + require.NoError(t, err) + req.Body = io.NopCloser(bytes.NewReader(jsonData)) + }, + Expected: func(t *testing.T, resp *http.Response) { + contentType := resp.Header.Get("Content-Type") + if contentType != "application/json; charset=utf-8" { + t.Fatalf("expected content type application/json; charset=utf-8, got %s", contentType) + } + _, err := io.ReadAll(resp.Body) + + if err != nil { + t.Fatal(err) + } + + if resp.StatusCode != http.StatusBadRequest { + t.Fatalf("expected status code 400, got %d", resp.StatusCode) + } + }, + }, } t.Setenv("OLLAMA_MODELS", t.TempDir()) @@ -420,3 +488,38 @@ func TestShow(t *testing.T) { t.Fatal("Expected projector architecture to be 'clip', but got", resp.ProjectorInfo["general.architecture"]) } } + +func TestNormalize(t *testing.T) { + type testCase struct { + input []float32 + } + + testCases := []testCase{ + {input: []float32{1}}, + {input: []float32{0, 1, 2, 3}}, + {input: []float32{0.1, 0.2, 0.3}}, + {input: []float32{-0.1, 0.2, 0.3, -0.4}}, + {input: []float32{0, 0, 0}}, + } + + isNormalized := func(vec []float32) (res bool) { + sum := 0.0 + for _, v := range vec { + sum += float64(v * v) + } + if math.Abs(sum-1) > 1e-6 { + return sum == 0 + } else { + return true + } + } + + for _, tc := range testCases { + t.Run("", func(t *testing.T) { + normalized := normalize(tc.input) + if !isNormalized(normalized) { + t.Errorf("Vector %v is not normalized", tc.input) + } + }) + } +} diff --git a/server/sched_test.go b/server/sched_test.go index 3fbd188a7..4b000331e 100644 --- a/server/sched_test.go +++ b/server/sched_test.go @@ -642,8 +642,8 @@ type mockLlm struct { pingResp error waitResp error completionResp error - embeddingResp []float64 - embeddingRespErr error + embedResp [][]float32 + embedRespErr error tokenizeResp []int tokenizeRespErr error detokenizeResp string @@ -660,8 +660,8 @@ func (s *mockLlm) WaitUntilRunning(ctx context.Context) error { return s.waitRes func (s *mockLlm) Completion(ctx context.Context, req llm.CompletionRequest, fn func(llm.CompletionResponse)) error { return s.completionResp } -func (s *mockLlm) Embedding(ctx context.Context, prompt string) ([]float64, error) { - return s.embeddingResp, s.embeddingRespErr +func (s *mockLlm) Embed(ctx context.Context, input []string) ([][]float32, error) { + return s.embedResp, s.embedRespErr } func (s *mockLlm) Tokenize(ctx context.Context, content string) ([]int, error) { return s.tokenizeResp, s.tokenizeRespErr From 9e35d9bbee4c96ca064bcb7eadc5b2eb3a200ce7 Mon Sep 17 00:00:00 2001 From: Jeffrey Morgan Date: Mon, 15 Jul 2024 13:55:57 -0700 Subject: [PATCH 087/384] server: lowercase roles for compatibility with clients (#5695) --- api/types.go | 16 ++++++++++++++-- api/types_test.go | 23 +++++++++++++++++++++++ 2 files changed, 37 insertions(+), 2 deletions(-) diff --git a/api/types.go b/api/types.go index bf5529283..3b607cecb 100644 --- a/api/types.go +++ b/api/types.go @@ -110,6 +110,18 @@ type Message struct { Images []ImageData `json:"images,omitempty"` } +func (m *Message) UnmarshalJSON(b []byte) error { + type Alias Message + var a Alias + if err := json.Unmarshal(b, &a); err != nil { + return err + } + + *m = Message(a) + m.Role = strings.ToLower(m.Role) + return nil +} + // ChatResponse is the response returned by [Client.Chat]. Its fields are // similar to [GenerateResponse]. type ChatResponse struct { @@ -243,8 +255,8 @@ type DeleteRequest struct { // ShowRequest is the request passed to [Client.Show]. type ShowRequest struct { - Model string `json:"model"` - System string `json:"system"` + Model string `json:"model"` + System string `json:"system"` // Template is deprecated Template string `json:"template"` diff --git a/api/types_test.go b/api/types_test.go index c60ed90e0..4699c1503 100644 --- a/api/types_test.go +++ b/api/types_test.go @@ -208,3 +208,26 @@ func TestUseMmapFormatParams(t *testing.T) { }) } } + +func TestMessage_UnmarshalJSON(t *testing.T) { + tests := []struct { + input string + expected string + }{ + {`{"role": "USER", "content": "Hello!"}`, "user"}, + {`{"role": "System", "content": "Initialization complete."}`, "system"}, + {`{"role": "assistant", "content": "How can I help you?"}`, "assistant"}, + {`{"role": "TOOl", "content": "Access granted."}`, "tool"}, + } + + for _, test := range tests { + var msg Message + if err := json.Unmarshal([]byte(test.input), &msg); err != nil { + t.Errorf("Unexpected error: %v", err) + } + + if msg.Role != test.expected { + t.Errorf("role not lowercased: got %v, expected %v", msg.Role, test.expected) + } + } +} From 224337b32f26e813ab0cf4a6544c1316715a5d41 Mon Sep 17 00:00:00 2001 From: Daniel Hiltgen Date: Mon, 15 Jul 2024 15:10:22 -0700 Subject: [PATCH 088/384] Bump linux ROCm to 6.1.2 --- .github/workflows/test.yaml | 2 +- Dockerfile | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 977d8da14..90fef6e59 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -126,7 +126,7 @@ jobs: strategy: matrix: rocm-version: - - '6.1.1' + - '6.1.2' runs-on: linux container: rocm/dev-ubuntu-20.04:${{ matrix.rocm-version }} steps: diff --git a/Dockerfile b/Dockerfile index b2c5c4a2f..ca3934964 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,7 +2,7 @@ ARG GOLANG_VERSION=1.22.1 ARG CMAKE_VERSION=3.22.1 # this CUDA_VERSION corresponds with the one specified in docs/gpu.md ARG CUDA_VERSION=11.3.1 -ARG ROCM_VERSION=6.1.1 +ARG ROCM_VERSION=6.1.2 # Copy the minimal context we need to run the generate scripts FROM scratch AS llm-code From d02bbebb11c2e9c391ee3af30ba3437e67d1b7a8 Mon Sep 17 00:00:00 2001 From: Michael Yang Date: Thu, 20 Jun 2024 13:45:47 -0700 Subject: [PATCH 089/384] tools --- api/types.go | 39 ++++++++++++- server/images.go | 11 +++- server/model.go | 105 ++++++++++++++++++++++++++++++++++ server/prompt.go | 6 +- server/prompt_test.go | 2 +- server/routes.go | 24 ++++++-- template/template.go | 129 +++++++++++++++++++++++++++++------------- 7 files changed, 263 insertions(+), 53 deletions(-) diff --git a/api/types.go b/api/types.go index 3b607cecb..97af4aed0 100644 --- a/api/types.go +++ b/api/types.go @@ -97,6 +97,9 @@ type ChatRequest struct { // followin the request. KeepAlive *Duration `json:"keep_alive,omitempty"` + // Tools is an optional list of tools the model has access to. + Tools []Tool `json:"tools,omitempty"` + // Options lists model-specific options. Options map[string]interface{} `json:"options"` } @@ -105,9 +108,36 @@ type ChatRequest struct { // role ("system", "user", or "assistant"), the content and an optional list // of images. type Message struct { - Role string `json:"role"` - Content string `json:"content"` - Images []ImageData `json:"images,omitempty"` + Role string `json:"role"` + Content string `json:"content,omitempty"` + Images []ImageData `json:"images,omitempty"` + ToolCalls []ToolCall `json:"tool_calls,omitempty"` +} + +type ToolCall struct { + ID string `json:"id"` + Type string `json:"type"` + Function struct { + Name string `json:"name"` + Arguments map[string]any `json:"arguments"` + } `json:"function"` +} + +type Tool struct { + Type string `json:"type"` + Function struct { + Name string `json:"name"` + Description string `json:"description"` + Parameters struct { + Type string `json:"type"` + Required []string `json:"required"` + Properties map[string]struct { + Type string `json:"type"` + Description string `json:"description"` + Enum []string `json:"enum,omitempty"` + } `json:"properties"` + } `json:"parameters"` + } `json:"function"` } func (m *Message) UnmarshalJSON(b []byte) error { @@ -374,6 +404,9 @@ type GenerateResponse struct { // Response is the textual response itself. Response string `json:"response"` + // ToolCalls is the list of tools the model wants to call + ToolCalls []ToolCall `json:"tool_calls,omitempty"` + // Done specifies if the response is complete. Done bool `json:"done"` diff --git a/server/images.go b/server/images.go index 688d5dcae..1b87888ed 100644 --- a/server/images.go +++ b/server/images.go @@ -38,7 +38,10 @@ var errCapabilityCompletion = errors.New("completion") type Capability string -const CapabilityCompletion = Capability("completion") +const ( + CapabilityCompletion = Capability("completion") + CapabilityTools = Capability("tools") +) type registryOptions struct { Insecure bool @@ -88,6 +91,10 @@ func (m *Model) CheckCapabilities(caps ...Capability) error { if _, ok := ggml.KV()[fmt.Sprintf("%s.pooling_type", ggml.KV().Architecture())]; ok { errs = append(errs, errCapabilityCompletion) } + case CapabilityTools: + if !slices.Contains(m.Template.Vars(), "tools") { + errs = append(errs, errors.New("tools")) + } default: slog.Error("unknown capability", "capability", cap) return fmt.Errorf("unknown capability: %s", cap) @@ -95,7 +102,7 @@ func (m *Model) CheckCapabilities(caps ...Capability) error { } if err := errors.Join(errs...); err != nil { - return fmt.Errorf("missing capabilities: %w", errors.Join(errs...)) + return fmt.Errorf("does not support %w", errors.Join(errs...)) } return nil diff --git a/server/model.go b/server/model.go index a79f549a3..be318db9c 100644 --- a/server/model.go +++ b/server/model.go @@ -4,6 +4,7 @@ import ( "archive/zip" "bytes" "context" + "encoding/json" "errors" "fmt" "io" @@ -11,7 +12,11 @@ import ( "net/http" "os" "path/filepath" + "slices" + "strings" + "text/template/parse" + "github.com/google/uuid" "github.com/ollama/ollama/api" "github.com/ollama/ollama/convert" "github.com/ollama/ollama/llm" @@ -289,3 +294,103 @@ func detectContentType(r io.Reader) (string, error) { return "unknown", nil } + +// parseToolCalls attempts to parse a JSON string into a slice of ToolCalls. +// mxyng: this only really works if the input contains tool calls in some JSON format +func (m *Model) parseToolCalls(s string) ([]api.ToolCall, bool) { + // create a subtree from the node that ranges over .ToolCalls + tmpl := m.Template.Subtree(func(n parse.Node) bool { + if t, ok := n.(*parse.RangeNode); ok { + return slices.Contains(template.Identifiers(t.Pipe), "ToolCalls") + } + + return false + }) + + if tmpl == nil { + return nil, false + } + + var b bytes.Buffer + if err := tmpl.Execute(&b, map[string][]map[string]any{ + "ToolCalls": { + { + "Function": map[string]any{ + "Name": "@@name@@", + "Arguments": "@@arguments@@", + }, + }, + }, + }); err != nil { + return nil, false + } + + var kv map[string]string + // execute the subtree with placeholders to identify the keys + if err := json.Unmarshal(b.Bytes(), &kv); err != nil { + return nil, false + } + + // find the keys that correspond to the name and arguments fields + var name, arguments string + for k, v := range kv { + switch v { + case "@@name@@": + name = k + case "@@arguments@@": + arguments = k + } + } + + var sm []map[string]any + decoder := json.NewDecoder(strings.NewReader(s)) + for { + // incrementally decode the JSON into a list of JSON objects + // skipping over any invalid tokens + if err := decoder.Decode(&sm); err != nil { + if errors.Is(err, io.EOF) { + break + } + + if errors.As(err, new(*json.SyntaxError)) { + r := decoder.Buffered() + if _, err := r.Read(make([]byte, decoder.InputOffset()+1)); err != nil { + break + } + + decoder = json.NewDecoder(r) + continue + } + + return nil, false + } + + // break as soon as a valid object is decoded + break + } + + var toolCalls []api.ToolCall + for _, kv := range sm { + call := api.ToolCall{ + ID: uuid.New().String(), + Type: "function", + } + + for k, v := range kv { + switch k { + case name: + call.Function.Name = v.(string) + case arguments: + call.Function.Arguments = v.(map[string]any) + } + } + + toolCalls = append(toolCalls, call) + } + + if len(toolCalls) > 0 { + return toolCalls, true + } + + return nil, false +} diff --git a/server/prompt.go b/server/prompt.go index abc5e61e1..be0d49692 100644 --- a/server/prompt.go +++ b/server/prompt.go @@ -15,7 +15,7 @@ type tokenizeFunc func(context.Context, string) ([]int, error) // chatPrompt accepts a list of messages and returns the prompt and images that should be used for the next chat turn. // chatPrompt truncates any messages that exceed the context window of the model, making sure to always include 1) the // latest message and 2) system messages -func chatPrompt(ctx context.Context, m *Model, tokenize tokenizeFunc, opts *api.Options, msgs []api.Message) (prompt string, images []llm.ImageData, _ error) { +func chatPrompt(ctx context.Context, m *Model, tokenize tokenizeFunc, opts *api.Options, msgs []api.Message, tools []api.Tool) (prompt string, images []llm.ImageData, _ error) { var system []api.Message // always include the last message n := len(msgs) - 1 @@ -29,7 +29,7 @@ func chatPrompt(ctx context.Context, m *Model, tokenize tokenizeFunc, opts *api. } var b bytes.Buffer - if err := m.Template.Execute(&b, template.Values{Messages: append(system, msgs[i:]...)}); err != nil { + if err := m.Template.Execute(&b, template.Values{Messages: append(system, msgs[i:]...), Tools: tools}); err != nil { return "", nil, err } @@ -57,7 +57,7 @@ func chatPrompt(ctx context.Context, m *Model, tokenize tokenizeFunc, opts *api. // truncate any messages that do not fit into the context window var b bytes.Buffer - if err := m.Template.Execute(&b, template.Values{Messages: append(system, msgs[n:]...)}); err != nil { + if err := m.Template.Execute(&b, template.Values{Messages: append(system, msgs[n:]...), Tools: tools}); err != nil { return "", nil, err } diff --git a/server/prompt_test.go b/server/prompt_test.go index d8caf3ed2..9c4da0685 100644 --- a/server/prompt_test.go +++ b/server/prompt_test.go @@ -192,7 +192,7 @@ func TestChatPrompt(t *testing.T) { t.Run(tt.name, func(t *testing.T) { model := Model{Template: tmpl, ProjectorPaths: []string{"vision"}} opts := api.Options{Runner: api.Runner{NumCtx: tt.limit}} - prompt, images, err := chatPrompt(context.TODO(), &model, tokenize, &opts, tt.msgs) + prompt, images, err := chatPrompt(context.TODO(), &model, tokenize, &opts, tt.msgs, nil) if err != nil { t.Fatal(err) } diff --git a/server/routes.go b/server/routes.go index c5c3a19ca..9712d8950 100644 --- a/server/routes.go +++ b/server/routes.go @@ -265,6 +265,11 @@ func (s *Server) GenerateHandler(c *gin.Context) { } r.Response = sb.String() + if toolCalls, ok := m.parseToolCalls(sb.String()); ok { + r.ToolCalls = toolCalls + r.Response = "" + } + c.JSON(http.StatusOK, r) return } @@ -1279,6 +1284,10 @@ func (s *Server) ChatHandler(c *gin.Context) { } caps := []Capability{CapabilityCompletion} + if req.Tools != nil { + caps = append(caps, CapabilityTools) + } + r, m, opts, err := s.scheduleRunner(c.Request.Context(), req.Model, caps, req.Options, req.KeepAlive) if errors.Is(err, errCapabilityCompletion) { c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Sprintf("%q does not support chat", req.Model)}) @@ -1305,7 +1314,7 @@ func (s *Server) ChatHandler(c *gin.Context) { req.Messages = append([]api.Message{{Role: "system", Content: m.System}}, req.Messages...) } - prompt, images, err := chatPrompt(c.Request.Context(), m, r.Tokenize, opts, req.Messages) + prompt, images, err := chatPrompt(c.Request.Context(), m, r.Tokenize, opts, req.Messages, req.Tools) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return @@ -1348,13 +1357,13 @@ func (s *Server) ChatHandler(c *gin.Context) { }() if req.Stream != nil && !*req.Stream { - var r api.ChatResponse + var resp api.ChatResponse var sb strings.Builder for rr := range ch { switch t := rr.(type) { case api.ChatResponse: sb.WriteString(t.Message.Content) - r = t + resp = t case gin.H: msg, ok := t["error"].(string) if !ok { @@ -1369,8 +1378,13 @@ func (s *Server) ChatHandler(c *gin.Context) { } } - r.Message.Content = sb.String() - c.JSON(http.StatusOK, r) + resp.Message.Content = sb.String() + if toolCalls, ok := m.parseToolCalls(sb.String()); ok { + resp.Message.ToolCalls = toolCalls + resp.Message.Content = "" + } + + c.JSON(http.StatusOK, resp) return } diff --git a/template/template.go b/template/template.go index 90014ec1a..0e23cf1ce 100644 --- a/template/template.go +++ b/template/template.go @@ -13,6 +13,7 @@ import ( "sync" "text/template" "text/template/parse" + "time" "github.com/agnivade/levenshtein" "github.com/ollama/ollama/api" @@ -102,8 +103,18 @@ var response = parse.ActionNode{ }, } +var funcs = template.FuncMap{ + "json": func(v any) string { + b, _ := json.Marshal(v) + return string(b) + }, + "now": func() string { + return time.Now().Format("2006-01-02 15:04:05") + }, +} + func Parse(s string) (*Template, error) { - tmpl := template.New("").Option("missingkey=zero") + tmpl := template.New("").Option("missingkey=zero").Funcs(funcs) tmpl, err := tmpl.Parse(s) if err != nil { @@ -127,7 +138,7 @@ func (t *Template) Vars() []string { var vars []string for _, tt := range t.Templates() { for _, n := range tt.Root.Nodes { - vars = append(vars, parseNode(n)...) + vars = append(vars, Identifiers(n)...) } } @@ -143,17 +154,65 @@ func (t *Template) Vars() []string { type Values struct { Messages []api.Message + Tools []api.Tool // forceLegacy is a flag used to test compatibility with legacy templates forceLegacy bool } +func (t *Template) Subtree(fn func(parse.Node) bool) *template.Template { + var walk func(parse.Node) parse.Node + walk = func(n parse.Node) parse.Node { + if fn(n) { + return n + } + + switch t := n.(type) { + case *parse.ListNode: + for _, c := range t.Nodes { + if n := walk(c); n != nil { + return n + } + } + case *parse.BranchNode: + for _, n := range []*parse.ListNode{t.List, t.ElseList} { + if n != nil { + if n := walk(n); n != nil { + return n + } + } + } + case *parse.IfNode: + return walk(&t.BranchNode) + case *parse.WithNode: + return walk(&t.BranchNode) + case *parse.RangeNode: + return walk(&t.BranchNode) + } + + return nil + } + + if n := walk(t.Tree.Root); n != nil { + return (&template.Template{ + Tree: &parse.Tree{ + Root: &parse.ListNode{ + Nodes: []parse.Node{n}, + }, + }, + }).Funcs(funcs) + } + + return nil +} + func (t *Template) Execute(w io.Writer, v Values) error { system, messages := collate(v.Messages) if !v.forceLegacy && slices.Contains(t.Vars(), "messages") { return t.Template.Execute(w, map[string]any{ "System": system, "Messages": messages, + "Tools": v.Tools, }) } @@ -161,7 +220,7 @@ func (t *Template) Execute(w io.Writer, v Values) error { var b bytes.Buffer var prompt, response string for _, m := range messages { - execute := func () error { + execute := func() error { if err := t.Template.Execute(&b, map[string]any{ "System": system, "Prompt": prompt, @@ -198,12 +257,8 @@ func (t *Template) Execute(w io.Writer, v Values) error { var cut bool nodes := deleteNode(t.Template.Root.Copy(), func(n parse.Node) bool { - switch t := n.(type) { - case *parse.ActionNode: - case *parse.FieldNode: - if slices.Contains(t.Ident, "Response") { - cut = true - } + if field, ok := n.(*parse.FieldNode); ok && slices.Contains(field.Ident, "Response") { + cut = true } return cut @@ -255,50 +310,46 @@ func collate(msgs []api.Message) (string, []*api.Message) { return strings.Join(system, "\n\n"), collated } -func parseNode(n parse.Node) []string { +// Identifiers walks the node tree returning any identifiers it finds along the way +func Identifiers(n parse.Node) []string { switch n := n.(type) { + case *parse.ListNode: + var names []string + for _, n := range n.Nodes { + names = append(names, Identifiers(n)...) + } + + return names + case *parse.TemplateNode: + return Identifiers(n.Pipe) case *parse.ActionNode: - return parseNode(n.Pipe) + return Identifiers(n.Pipe) + case *parse.BranchNode: + names := Identifiers(n.Pipe) + for _, n := range []*parse.ListNode{n.List, n.ElseList} { + if n != nil { + names = append(names, Identifiers(n)...) + } + } + return names case *parse.IfNode: - names := parseNode(n.Pipe) - names = append(names, parseNode(n.List)...) - if n.ElseList != nil { - names = append(names, parseNode(n.ElseList)...) - } - return names + return Identifiers(&n.BranchNode) case *parse.RangeNode: - names := parseNode(n.Pipe) - names = append(names, parseNode(n.List)...) - if n.ElseList != nil { - names = append(names, parseNode(n.ElseList)...) - } - return names + return Identifiers(&n.BranchNode) case *parse.WithNode: - names := parseNode(n.Pipe) - names = append(names, parseNode(n.List)...) - if n.ElseList != nil { - names = append(names, parseNode(n.ElseList)...) - } - return names + return Identifiers(&n.BranchNode) case *parse.PipeNode: var names []string for _, c := range n.Cmds { for _, a := range c.Args { - names = append(names, parseNode(a)...) + names = append(names, Identifiers(a)...) } } - return names - case *parse.ListNode: - var names []string - for _, n := range n.Nodes { - names = append(names, parseNode(n)...) - } - return names case *parse.FieldNode: return n.Ident - case *parse.TemplateNode: - return parseNode(n.Pipe) + case *parse.VariableNode: + return n.Ident } return nil From ef5136a745138896d080bf5bcac13377f7672b77 Mon Sep 17 00:00:00 2001 From: Michael Yang Date: Mon, 15 Jul 2024 12:17:38 -0700 Subject: [PATCH 090/384] tools test --- server/model_test.go | 122 ++++++++++++++++++++ server/testdata/tools/command-r-plus.gotmpl | 67 +++++++++++ server/testdata/tools/command-r-plus.out | 39 +++++++ server/testdata/tools/firefunction.gotmpl | 31 +++++ server/testdata/tools/firefunction.out | 17 +++ server/testdata/tools/messages.json | 39 +++++++ server/testdata/tools/mistral.gotmpl | 15 +++ server/testdata/tools/mistral.out | 3 + server/testdata/tools/tools.json | 30 +++++ template/template.go | 4 - 10 files changed, 363 insertions(+), 4 deletions(-) create mode 100644 server/testdata/tools/command-r-plus.gotmpl create mode 100644 server/testdata/tools/command-r-plus.out create mode 100644 server/testdata/tools/firefunction.gotmpl create mode 100644 server/testdata/tools/firefunction.out create mode 100644 server/testdata/tools/messages.json create mode 100644 server/testdata/tools/mistral.gotmpl create mode 100644 server/testdata/tools/mistral.out create mode 100644 server/testdata/tools/tools.json diff --git a/server/model_test.go b/server/model_test.go index a383b7e72..025781928 100644 --- a/server/model_test.go +++ b/server/model_test.go @@ -3,7 +3,9 @@ package server import ( "archive/zip" "bytes" + "encoding/json" "errors" + "fmt" "io" "os" "path/filepath" @@ -11,7 +13,9 @@ import ( "strings" "testing" + "github.com/google/go-cmp/cmp" "github.com/ollama/ollama/api" + "github.com/ollama/ollama/template" ) func createZipFile(t *testing.T, name string) *os.File { @@ -110,3 +114,121 @@ func TestExtractFromZipFile(t *testing.T) { }) } } + +type function struct { + Name string `json:"name"` + Arguments map[string]any `json:"arguments"` +} + +func readFile(t *testing.T, base, name string) *bytes.Buffer { + t.Helper() + + bts, err := os.ReadFile(filepath.Join(base, name)) + if err != nil { + t.Fatal(err) + } + + return bytes.NewBuffer(bts) +} + +func TestExecuteWithTools(t *testing.T) { + p := filepath.Join("testdata", "tools") + cases := []struct { + model string + output string + }{ + {"mistral", `[TOOL_CALLS] [{"name": "get_current_weather", "arguments": {"format":"fahrenheit","location":"San Francisco, CA"}},{"name": "get_current_weather", "arguments": {"format":"celsius","location":"Toronto, Canada"}}]`}, + {"mistral", `[TOOL_CALLS] [{"name": "get_current_weather", "arguments": {"format":"fahrenheit","location":"San Francisco, CA"}},{"name": "get_current_weather", "arguments": {"format":"celsius","location":"Toronto, Canada"}}] + +The temperature in San Francisco, CA is 70°F and in Toronto, Canada is 20°C.`}, + {"command-r-plus", "Action: ```json" + ` +[ + { + "tool_name": "get_current_weather", + "parameters": { + "format": "fahrenheit", + "location": "San Francisco, CA" + } + }, + { + "tool_name": "get_current_weather", + "parameters": { + "format": "celsius", + "location": "Toronto, Canada" + } + } +] +` + "```"}, + {"firefunction", ` functools[{"name": "get_current_weather", "arguments": {"format":"fahrenheit","location":"San Francisco, CA"}},{"name": "get_current_weather", "arguments": {"format":"celsius","location":"Toronto, Canada"}}]`}, + } + + var tools []api.Tool + if err := json.Unmarshal(readFile(t, p, "tools.json").Bytes(), &tools); err != nil { + t.Fatal(err) + } + + var messages []api.Message + if err := json.Unmarshal(readFile(t, p, "messages.json").Bytes(), &messages); err != nil { + t.Fatal(err) + } + + calls := []api.ToolCall{ + { + Type: "function", + Function: function{ + Name: "get_current_weather", + Arguments: map[string]any{ + "format": "fahrenheit", + "location": "San Francisco, CA", + }, + }, + }, + { + Type: "function", + Function: function{ + Name: "get_current_weather", + Arguments: map[string]any{ + "format": "celsius", + "location": "Toronto, Canada", + }, + }, + }, + } + + for _, tt := range cases { + t.Run(tt.model, func(t *testing.T) { + tmpl, err := template.Parse(readFile(t, p, fmt.Sprintf("%s.gotmpl", tt.model)).String()) + if err != nil { + t.Fatal(err) + } + + t.Run("template", func(t *testing.T) { + var actual bytes.Buffer + if err := tmpl.Execute(&actual, template.Values{Tools: tools, Messages: messages}); err != nil { + t.Fatal(err) + } + + if diff := cmp.Diff(actual.String(), readFile(t, p, fmt.Sprintf("%s.out", tt.model)).String()); diff != "" { + t.Errorf("mismatch (-got +want):\n%s", diff) + } + }) + + t.Run("parse", func(t *testing.T) { + m := &Model{Template: tmpl} + actual, ok := m.parseToolCalls(tt.output) + if !ok { + t.Fatal("failed to parse tool calls") + } + + for i := range actual { + // ID is randomly generated so clear it for comparison + actual[i].ID = "" + } + + if diff := cmp.Diff(actual, calls); diff != "" { + t.Errorf("mismatch (-got +want):\n%s", diff) + } + }) + }) + } +} diff --git a/server/testdata/tools/command-r-plus.gotmpl b/server/testdata/tools/command-r-plus.gotmpl new file mode 100644 index 000000000..088a4f0e5 --- /dev/null +++ b/server/testdata/tools/command-r-plus.gotmpl @@ -0,0 +1,67 @@ +{{- if or .Tools .System }}<|START_OF_TURN_TOKEN|><|SYSTEM_TOKEN|> +{{- if .Tools }}# Safety Preamble +The instructions in this section override those in the task description and style guide sections. Don't answer questions that are harmful or immoral. + +# System Preamble +## Basic Rules +You are a powerful conversational AI trained by Cohere to help people. You are augmented by a number of tools, and your job is to use and consume the output of these tools to best help the user. You will see a conversation history between yourself and a user, ending with an utterance from the user. You will then see a specific instruction instructing you what kind of response to generate. When you answer the user's requests, you cite your sources in your answers, according to those instructions. + +{{ if .System }}# User Preamble +{{ .System }} +{{- end }} + +## Available Tools +Here is a list of tools that you have available to you: +{{- range .Tools }} + +```python +def {{ .Function.Name }}( +{{- range $name, $property := .Function.Parameters.Properties }}{{ $name }}: {{ $property.Type }}, {{ end }}) -> List[Dict]: + """{{ .Function.Description }} + +{{- if .Function.Parameters.Properties }} + + Args: +{{- range $name, $property := .Function.Parameters.Properties }} + {{ $name }} ({{ $property.Type }}): {{ $property.Description }} +{{- end }} +{{- end }} + """ + pass +``` +{{- end }} +{{- else if .System }}{{ .System }} +{{- end }}<|END_OF_TURN_TOKEN|> +{{- end }} +{{- range .Messages }} +{{- if eq .Role "system" }} +{{- continue }} +{{- end }}<|START_OF_TURN_TOKEN|> +{{- if eq .Role "user" }}<|USER_TOKEN|>{{ .Content }} +{{- else if eq .Role "assistant" }}<|CHATBOT_TOKEN|> +{{- if .Content }}{{ .Content }} +{{- else if .ToolCalls }} +Action: ```json +[ +{{- range .ToolCalls }} + { + "tool_name": "{{ .Function.Name }}", + "parameters": {{ json .Function.Arguments }} + } +{{- end }} +]``` +{{ continue }} +{{ end }} +{{- else if eq .Role "tool" }}<|SYSTEM_TOKEN|> +{{ .Content }} +{{- end }}<|END_OF_TURN_TOKEN|> +{{- end }} +{{- if .Tools }}<|START_OF_TURN_TOKEN|><|SYSTEM_TOKEN|>Write 'Action:' followed by a json-formatted list of actions that you want to perform in order to produce a good response to the user's last input. You can use any of the supplied tools any number of times, but you should aim to execute the minimum number of necessary actions for the input. You should use the `directly-answer` tool if calling the other tools is unnecessary. The list of actions you want to call should be formatted as a list of json objects, for example: +```json +[ + { + "tool_name": title of the tool in the specification, + "parameters": a dict of parameters to input into the tool as they are defined in the specs, or {} if it takes no parameters + } +]``` +{{- end }}<|END_OF_TURN_TOKEN|><|START_OF_TURN_TOKEN|><|CHATBOT_TOKEN|> \ No newline at end of file diff --git a/server/testdata/tools/command-r-plus.out b/server/testdata/tools/command-r-plus.out new file mode 100644 index 000000000..425af75ab --- /dev/null +++ b/server/testdata/tools/command-r-plus.out @@ -0,0 +1,39 @@ +<|START_OF_TURN_TOKEN|><|SYSTEM_TOKEN|># Safety Preamble +The instructions in this section override those in the task description and style guide sections. Don't answer questions that are harmful or immoral. + +# System Preamble +## Basic Rules +You are a powerful conversational AI trained by Cohere to help people. You are augmented by a number of tools, and your job is to use and consume the output of these tools to best help the user. You will see a conversation history between yourself and a user, ending with an utterance from the user. You will then see a specific instruction instructing you what kind of response to generate. When you answer the user's requests, you cite your sources in your answers, according to those instructions. + +# User Preamble +You are a knowledgable assistant. You can answer questions and perform tasks. + +## Available Tools +Here is a list of tools that you have available to you: + +```python +def get_current_weather(format: string, location: string, ) -> List[Dict]: + """Get the current weather + + Args: + format (string): The temperature unit to use. Infer this from the users location. + location (string): The city and state, e.g. San Francisco, CA + """ + pass +```<|END_OF_TURN_TOKEN|><|START_OF_TURN_TOKEN|><|USER_TOKEN|>What's the weather like today in Paris?<|END_OF_TURN_TOKEN|><|START_OF_TURN_TOKEN|><|CHATBOT_TOKEN|> +Action: ```json +[ + { + "tool_name": "get_current_weather", + "parameters": {"format":"celsius","location":"Paris, France"} + } +]``` +<|START_OF_TURN_TOKEN|><|SYSTEM_TOKEN|> +22<|END_OF_TURN_TOKEN|><|START_OF_TURN_TOKEN|><|CHATBOT_TOKEN|>The current temperature in Paris, France is 22 degrees Celsius.<|END_OF_TURN_TOKEN|><|START_OF_TURN_TOKEN|><|USER_TOKEN|>What's the weather like today in San Francisco and Toronto?<|END_OF_TURN_TOKEN|><|START_OF_TURN_TOKEN|><|SYSTEM_TOKEN|>Write 'Action:' followed by a json-formatted list of actions that you want to perform in order to produce a good response to the user's last input. You can use any of the supplied tools any number of times, but you should aim to execute the minimum number of necessary actions for the input. You should use the `directly-answer` tool if calling the other tools is unnecessary. The list of actions you want to call should be formatted as a list of json objects, for example: +```json +[ + { + "tool_name": title of the tool in the specification, + "parameters": a dict of parameters to input into the tool as they are defined in the specs, or {} if it takes no parameters + } +]```<|END_OF_TURN_TOKEN|><|START_OF_TURN_TOKEN|><|CHATBOT_TOKEN|> \ No newline at end of file diff --git a/server/testdata/tools/firefunction.gotmpl b/server/testdata/tools/firefunction.gotmpl new file mode 100644 index 000000000..bca88b3bd --- /dev/null +++ b/server/testdata/tools/firefunction.gotmpl @@ -0,0 +1,31 @@ +{{- if or .System .Tools }}<|start_header_id|>system<|end_header_id|> +{{- if .System }} +{{ .System }} +{{- end }} +In addition to plain text responses, you can chose to call one or more of the provided functions. + +Use the following rule to decide when to call a function: + * if the response can be generated from your internal knowledge (e.g., as in the case of queries like "What is the capital of Poland?"), do so + * if you need external information that can be obtained by calling one or more of the provided functions, generate a function calls + +If you decide to call functions: + * prefix function calls with functools marker (no closing marker required) + * all function calls should be generated in a single JSON list formatted as functools[{"name": [function name], "arguments": [function arguments as JSON]}, ...] + * follow the provided JSON schema. Do not hallucinate arguments or values. Do to blindly copy values from the provided samples + * respect the argument type formatting. E.g., if the type if number and format is float, write value 7 as 7.0 + * make sure you pick the right functions that match the user intent + +Available functions as JSON spec: +{{- if .Tools }} +{{ json .Tools }} +{{- end }}<|eot_id|> +{{- end }} +{{- range .Messages }}<|start_header_id|> +{{- if or (eq .Role "user") (eq .Role "assistant") (eq .Role "tool") }}{{ .Role }} +{{- end }}<|end_header_id|> +{{- if .Content }}{{ .Content }} +{{- else if .ToolCalls }} functools[ +{{- range .ToolCalls }}{{ "{" }}"name": "{{ .Function.Name }}", "arguments": {{ json .Function.Arguments }}{{ "}" }} +{{- end }}] +{{- end }}<|eot_id|> +{{- end }}<|start_header_id|>assistant<|end_header_id|> \ No newline at end of file diff --git a/server/testdata/tools/firefunction.out b/server/testdata/tools/firefunction.out new file mode 100644 index 000000000..be50175ef --- /dev/null +++ b/server/testdata/tools/firefunction.out @@ -0,0 +1,17 @@ +<|start_header_id|>system<|end_header_id|> +You are a knowledgable assistant. You can answer questions and perform tasks. +In addition to plain text responses, you can chose to call one or more of the provided functions. + +Use the following rule to decide when to call a function: + * if the response can be generated from your internal knowledge (e.g., as in the case of queries like "What is the capital of Poland?"), do so + * if you need external information that can be obtained by calling one or more of the provided functions, generate a function calls + +If you decide to call functions: + * prefix function calls with functools marker (no closing marker required) + * all function calls should be generated in a single JSON list formatted as functools[{"name": [function name], "arguments": [function arguments as JSON]}, ...] + * follow the provided JSON schema. Do not hallucinate arguments or values. Do to blindly copy values from the provided samples + * respect the argument type formatting. E.g., if the type if number and format is float, write value 7 as 7.0 + * make sure you pick the right functions that match the user intent + +Available functions as JSON spec: +[{"type":"function","function":{"name":"get_current_weather","description":"Get the current weather","parameters":{"type":"object","required":["location","format"],"properties":{"format":{"type":"string","description":"The temperature unit to use. Infer this from the users location.","enum":["celsius","fahrenheit"]},"location":{"type":"string","description":"The city and state, e.g. San Francisco, CA"}}}}}]<|eot_id|><|start_header_id|><|end_header_id|>You are a knowledgable assistant. You can answer questions and perform tasks.<|eot_id|><|start_header_id|>user<|end_header_id|>What's the weather like today in Paris?<|eot_id|><|start_header_id|>assistant<|end_header_id|> functools[{"name": "get_current_weather", "arguments": {"format":"celsius","location":"Paris, France"}}]<|eot_id|><|start_header_id|>tool<|end_header_id|>22<|eot_id|><|start_header_id|>assistant<|end_header_id|>The current temperature in Paris, France is 22 degrees Celsius.<|eot_id|><|start_header_id|>user<|end_header_id|>What's the weather like today in San Francisco and Toronto?<|eot_id|><|start_header_id|>assistant<|end_header_id|> \ No newline at end of file diff --git a/server/testdata/tools/messages.json b/server/testdata/tools/messages.json new file mode 100644 index 000000000..1a3d1f56c --- /dev/null +++ b/server/testdata/tools/messages.json @@ -0,0 +1,39 @@ +[ + { + "role": "system", + "content": "You are a knowledgable assistant. You can answer questions and perform tasks." + }, + { + "role": "user", + "content": "What's the weather like today in Paris?" + }, + { + "role": "assistant", + "tool_calls": [ + { + "id": "89a1e453-0bce-4de3-a456-c54bed09c520", + "type": "function", + "function": { + "name": "get_current_weather", + "arguments": { + "location": "Paris, France", + "format": "celsius" + } + } + } + ] + }, + { + "role": "tool", + "tool_call_id": "89a1e453-0bce-4de3-a456-c54bed09c520", + "content": "22" + }, + { + "role": "assistant", + "content": "The current temperature in Paris, France is 22 degrees Celsius." + }, + { + "role": "user", + "content": "What's the weather like today in San Francisco and Toronto?" + } +] diff --git a/server/testdata/tools/mistral.gotmpl b/server/testdata/tools/mistral.gotmpl new file mode 100644 index 000000000..a98bc7ad6 --- /dev/null +++ b/server/testdata/tools/mistral.gotmpl @@ -0,0 +1,15 @@ +{{- range $index, $_ := .Messages }} +{{- if eq .Role "user" }} +{{- if and (eq (len (slice $.Messages $index)) 1) $.Tools }}[AVAILABLE_TOOLS] {{ json $.Tools }}[/AVAILABLE_TOOLS] +{{- end }}[INST] {{ if and (eq (len (slice $.Messages $index)) 1) $.System }}{{ $.System }} + +{{ end }}{{ .Content }}[/INST] +{{- else if eq .Role "assistant" }} +{{- if .Content }} {{ .Content }} +{{- else if .ToolCalls }}[TOOL_CALLS] [ +{{- range .ToolCalls }}{"name": "{{ .Function.Name }}", "arguments": {{ json .Function.Arguments }}} +{{- end }}] +{{- end }} +{{- else if eq .Role "tool" }}[TOOL_RESULTS] {"content": {{ .Content }}}[/TOOL_RESULTS] +{{- end }} +{{- end }} \ No newline at end of file diff --git a/server/testdata/tools/mistral.out b/server/testdata/tools/mistral.out new file mode 100644 index 000000000..31d8cdd62 --- /dev/null +++ b/server/testdata/tools/mistral.out @@ -0,0 +1,3 @@ +[INST] What's the weather like today in Paris?[/INST][TOOL_CALLS] [{"name": "get_current_weather", "arguments": {"format":"celsius","location":"Paris, France"}}][TOOL_RESULTS] {"content": 22}[/TOOL_RESULTS] The current temperature in Paris, France is 22 degrees Celsius.[AVAILABLE_TOOLS] [{"type":"function","function":{"name":"get_current_weather","description":"Get the current weather","parameters":{"type":"object","required":["location","format"],"properties":{"format":{"type":"string","description":"The temperature unit to use. Infer this from the users location.","enum":["celsius","fahrenheit"]},"location":{"type":"string","description":"The city and state, e.g. San Francisco, CA"}}}}}][/AVAILABLE_TOOLS][INST] You are a knowledgable assistant. You can answer questions and perform tasks. + +What's the weather like today in San Francisco and Toronto?[/INST] \ No newline at end of file diff --git a/server/testdata/tools/tools.json b/server/testdata/tools/tools.json new file mode 100644 index 000000000..17260bf83 --- /dev/null +++ b/server/testdata/tools/tools.json @@ -0,0 +1,30 @@ +[ + { + "type": "function", + "function": { + "name": "get_current_weather", + "description": "Get the current weather", + "parameters": { + "type": "object", + "properties": { + "location": { + "type": "string", + "description": "The city and state, e.g. San Francisco, CA" + }, + "format": { + "type": "string", + "enum": [ + "celsius", + "fahrenheit" + ], + "description": "The temperature unit to use. Infer this from the users location." + } + }, + "required": [ + "location", + "format" + ] + } + } + } +] diff --git a/template/template.go b/template/template.go index 0e23cf1ce..7cdb30ef1 100644 --- a/template/template.go +++ b/template/template.go @@ -13,7 +13,6 @@ import ( "sync" "text/template" "text/template/parse" - "time" "github.com/agnivade/levenshtein" "github.com/ollama/ollama/api" @@ -108,9 +107,6 @@ var funcs = template.FuncMap{ b, _ := json.Marshal(v) return string(b) }, - "now": func() string { - return time.Now().Format("2006-01-02 15:04:05") - }, } func Parse(s string) (*Template, error) { From 7ac6d462ecb9a26591b5f7457bea32c1cd63541f Mon Sep 17 00:00:00 2001 From: Jeffrey Morgan Date: Mon, 15 Jul 2024 17:39:44 -0700 Subject: [PATCH 091/384] server: return empty slice on empty `/api/embed` request (#5713) * server: return empty slice on empty `/api/embed` request * fix tests --- api/types.go | 2 +- server/routes_test.go | 8 ++++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/api/types.go b/api/types.go index 3b607cecb..649860a1f 100644 --- a/api/types.go +++ b/api/types.go @@ -206,7 +206,7 @@ type EmbedRequest struct { // EmbedResponse is the response from [Client.Embed]. type EmbedResponse struct { Model string `json:"model"` - Embeddings [][]float32 `json:"embeddings,omitempty"` + Embeddings [][]float32 `json:"embeddings"` } // EmbeddingRequest is the request passed to [Client.Embeddings]. diff --git a/server/routes_test.go b/server/routes_test.go index 70622e9b0..97786ba2b 100644 --- a/server/routes_test.go +++ b/server/routes_test.go @@ -306,8 +306,12 @@ func Test_Routes(t *testing.T) { t.Fatalf("expected model t-bone, got %s", embedResp.Model) } - if embedResp.Embeddings != nil { - t.Fatalf("expected embeddings to be nil, got %v", embedResp.Embeddings) + if embedResp.Embeddings == nil { + t.Fatalf("expected embeddings to not be nil, got %v", embedResp.Embeddings) + } + + if len(embedResp.Embeddings) != 0 { + t.Fatalf("expected embeddings to be empty, got %v", embedResp.Embeddings) } }, }, From 4a565cbf9417ab6ec4560d334d556b5e97e23be9 Mon Sep 17 00:00:00 2001 From: Michael Yang Date: Sat, 13 Jul 2024 17:46:24 -0700 Subject: [PATCH 092/384] add chat and generate tests with mock runner --- llm/gguf.go | 1 + server/prompt_test.go | 15 +- server/routes_create_test.go | 18 + server/routes_delete_test.go | 5 + server/routes_generate_test.go | 651 +++++++++++++++++++++++++++++++++ server/routes_list_test.go | 3 + 6 files changed, 679 insertions(+), 14 deletions(-) create mode 100644 server/routes_generate_test.go diff --git a/llm/gguf.go b/llm/gguf.go index 4d343a1bd..a8427aed8 100644 --- a/llm/gguf.go +++ b/llm/gguf.go @@ -537,6 +537,7 @@ var ggufKVOrder = map[string][]string{ "tokenizer.ggml.add_bos_token", "tokenizer.ggml.add_eos_token", "tokenizer.chat_template", + "bert.pooling_type", }, } diff --git a/server/prompt_test.go b/server/prompt_test.go index 9c4da0685..02d23785f 100644 --- a/server/prompt_test.go +++ b/server/prompt_test.go @@ -3,7 +3,6 @@ package server import ( "bytes" "context" - "strings" "testing" "github.com/google/go-cmp/cmp" @@ -11,14 +10,6 @@ import ( "github.com/ollama/ollama/template" ) -func tokenize(_ context.Context, s string) (tokens []int, err error) { - for range strings.Fields(s) { - tokens = append(tokens, len(tokens)) - } - - return -} - func TestChatPrompt(t *testing.T) { type expect struct { prompt string @@ -192,15 +183,11 @@ func TestChatPrompt(t *testing.T) { t.Run(tt.name, func(t *testing.T) { model := Model{Template: tmpl, ProjectorPaths: []string{"vision"}} opts := api.Options{Runner: api.Runner{NumCtx: tt.limit}} - prompt, images, err := chatPrompt(context.TODO(), &model, tokenize, &opts, tt.msgs, nil) + prompt, images, err := chatPrompt(context.TODO(), &model, mockRunner{}.Tokenize, &opts, tt.msgs, nil) if err != nil { t.Fatal(err) } - if tt.prompt != prompt { - t.Errorf("expected %q, got %q", tt.prompt, prompt) - } - if diff := cmp.Diff(prompt, tt.prompt); diff != "" { t.Errorf("mismatch (-got +want):\n%s", diff) } diff --git a/server/routes_create_test.go b/server/routes_create_test.go index 04174b92e..cb548ebda 100644 --- a/server/routes_create_test.go +++ b/server/routes_create_test.go @@ -85,6 +85,8 @@ func checkFileExists(t *testing.T, p string, expect []string) { } func TestCreateFromBin(t *testing.T) { + gin.SetMode(gin.TestMode) + p := t.TempDir() t.Setenv("OLLAMA_MODELS", p) envconfig.LoadConfig() @@ -111,6 +113,8 @@ func TestCreateFromBin(t *testing.T) { } func TestCreateFromModel(t *testing.T) { + gin.SetMode(gin.TestMode) + p := t.TempDir() t.Setenv("OLLAMA_MODELS", p) envconfig.LoadConfig() @@ -152,6 +156,8 @@ func TestCreateFromModel(t *testing.T) { } func TestCreateRemovesLayers(t *testing.T) { + gin.SetMode(gin.TestMode) + p := t.TempDir() t.Setenv("OLLAMA_MODELS", p) envconfig.LoadConfig() @@ -199,6 +205,8 @@ func TestCreateRemovesLayers(t *testing.T) { } func TestCreateUnsetsSystem(t *testing.T) { + gin.SetMode(gin.TestMode) + p := t.TempDir() t.Setenv("OLLAMA_MODELS", p) envconfig.LoadConfig() @@ -255,6 +263,8 @@ func TestCreateUnsetsSystem(t *testing.T) { } func TestCreateMergeParameters(t *testing.T) { + gin.SetMode(gin.TestMode) + p := t.TempDir() t.Setenv("OLLAMA_MODELS", p) envconfig.LoadConfig() @@ -358,6 +368,8 @@ func TestCreateMergeParameters(t *testing.T) { } func TestCreateReplacesMessages(t *testing.T) { + gin.SetMode(gin.TestMode) + p := t.TempDir() t.Setenv("OLLAMA_MODELS", p) envconfig.LoadConfig() @@ -434,6 +446,8 @@ func TestCreateReplacesMessages(t *testing.T) { } func TestCreateTemplateSystem(t *testing.T) { + gin.SetMode(gin.TestMode) + p := t.TempDir() t.Setenv("OLLAMA_MODELS", p) envconfig.LoadConfig() @@ -480,6 +494,8 @@ func TestCreateTemplateSystem(t *testing.T) { } func TestCreateLicenses(t *testing.T) { + gin.SetMode(gin.TestMode) + p := t.TempDir() t.Setenv("OLLAMA_MODELS", p) envconfig.LoadConfig() @@ -526,6 +542,8 @@ func TestCreateLicenses(t *testing.T) { } func TestCreateDetectTemplate(t *testing.T) { + gin.SetMode(gin.TestMode) + p := t.TempDir() t.Setenv("OLLAMA_MODELS", p) envconfig.LoadConfig() diff --git a/server/routes_delete_test.go b/server/routes_delete_test.go index 00303bd17..33a97a73d 100644 --- a/server/routes_delete_test.go +++ b/server/routes_delete_test.go @@ -8,12 +8,15 @@ import ( "path/filepath" "testing" + "github.com/gin-gonic/gin" "github.com/ollama/ollama/api" "github.com/ollama/ollama/envconfig" "github.com/ollama/ollama/types/model" ) func TestDelete(t *testing.T) { + gin.SetMode(gin.TestMode) + p := t.TempDir() t.Setenv("OLLAMA_MODELS", p) envconfig.LoadConfig() @@ -77,6 +80,8 @@ func TestDelete(t *testing.T) { } func TestDeleteDuplicateLayers(t *testing.T) { + gin.SetMode(gin.TestMode) + p := t.TempDir() t.Setenv("OLLAMA_MODELS", p) var s Server diff --git a/server/routes_generate_test.go b/server/routes_generate_test.go new file mode 100644 index 000000000..9d8993284 --- /dev/null +++ b/server/routes_generate_test.go @@ -0,0 +1,651 @@ +package server + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "io" + "net/http" + "strings" + "testing" + "time" + + "github.com/gin-gonic/gin" + "github.com/google/go-cmp/cmp" + + "github.com/ollama/ollama/api" + "github.com/ollama/ollama/gpu" + "github.com/ollama/ollama/llm" +) + +type mockRunner struct { + llm.LlamaServer + + // CompletionRequest is only valid until the next call to Completion + llm.CompletionRequest + llm.CompletionResponse +} + +func (m *mockRunner) Completion(_ context.Context, r llm.CompletionRequest, fn func(r llm.CompletionResponse)) error { + m.CompletionRequest = r + fn(m.CompletionResponse) + return nil +} + +func (mockRunner) Tokenize(_ context.Context, s string) (tokens []int, err error) { + for range strings.Fields(s) { + tokens = append(tokens, len(tokens)) + } + + return +} + +func newMockServer(mock *mockRunner) func(gpu.GpuInfoList, string, *llm.GGML, []string, []string, api.Options, int) (llm.LlamaServer, error) { + return func(gpus gpu.GpuInfoList, model string, ggml *llm.GGML, projectors, system []string, opts api.Options, numParallel int) (llm.LlamaServer, error) { + return mock, nil + } +} + +func TestGenerateChat(t *testing.T) { + gin.SetMode(gin.TestMode) + + mock := mockRunner{ + CompletionResponse: llm.CompletionResponse{ + Done: true, + DoneReason: "stop", + PromptEvalCount: 1, + PromptEvalDuration: 1, + EvalCount: 1, + EvalDuration: 1, + }, + } + + s := Server{ + sched: &Scheduler{ + pendingReqCh: make(chan *LlmRequest, 1), + finishedReqCh: make(chan *LlmRequest, 1), + expiredCh: make(chan *runnerRef, 1), + unloadedCh: make(chan any, 1), + loaded: make(map[string]*runnerRef), + newServerFn: newMockServer(&mock), + getGpuFn: gpu.GetGPUInfo, + getCpuFn: gpu.GetCPUInfo, + reschedDelay: 250 * time.Millisecond, + loadFn: func(req *LlmRequest, ggml *llm.GGML, gpus gpu.GpuInfoList, numParallel int) { + req.successCh <- &runnerRef{ + llama: &mock, + } + }, + }, + } + + go s.sched.Run(context.TODO()) + + w := createRequest(t, s.CreateModelHandler, api.CreateRequest{ + Name: "test", + Modelfile: fmt.Sprintf(`FROM %s + TEMPLATE """ +{{- if .System }}System: {{ .System }} {{ end }} +{{- if .Prompt }}User: {{ .Prompt }} {{ end }} +{{- if .Response }}Assistant: {{ .Response }} {{ end }}""" +`, createBinFile(t, llm.KV{ + "general.architecture": "llama", + "llama.block_count": uint32(1), + "llama.context_length": uint32(8192), + "llama.embedding_length": uint32(4096), + "llama.attention.head_count": uint32(32), + "llama.attention.head_count_kv": uint32(8), + "tokenizer.ggml.tokens": []string{""}, + "tokenizer.ggml.scores": []float32{0}, + "tokenizer.ggml.token_type": []int32{0}, + }, []llm.Tensor{ + {Name: "token_embd.weight", Shape: []uint64{1}, WriterTo: bytes.NewReader(make([]byte, 4))}, + {Name: "blk.0.attn_norm.weight", Shape: []uint64{1}, WriterTo: bytes.NewReader(make([]byte, 4))}, + {Name: "blk.0.ffn_down.weight", Shape: []uint64{1}, WriterTo: bytes.NewReader(make([]byte, 4))}, + {Name: "blk.0.ffn_gate.weight", Shape: []uint64{1}, WriterTo: bytes.NewReader(make([]byte, 4))}, + {Name: "blk.0.ffn_up.weight", Shape: []uint64{1}, WriterTo: bytes.NewReader(make([]byte, 4))}, + {Name: "blk.0.ffn_norm.weight", Shape: []uint64{1}, WriterTo: bytes.NewReader(make([]byte, 4))}, + {Name: "blk.0.attn_k.weight", Shape: []uint64{1}, WriterTo: bytes.NewReader(make([]byte, 4))}, + {Name: "blk.0.attn_output.weight", Shape: []uint64{1}, WriterTo: bytes.NewReader(make([]byte, 4))}, + {Name: "blk.0.attn_q.weight", Shape: []uint64{1}, WriterTo: bytes.NewReader(make([]byte, 4))}, + {Name: "blk.0.attn_v.weight", Shape: []uint64{1}, WriterTo: bytes.NewReader(make([]byte, 4))}, + {Name: "output.weight", Shape: []uint64{1}, WriterTo: bytes.NewReader(make([]byte, 4))}, + })), + Stream: &stream, + }) + + if w.Code != http.StatusOK { + t.Fatalf("expected status 200, got %d", w.Code) + } + + t.Run("missing body", func(t *testing.T) { + w := createRequest(t, s.ChatHandler, nil) + if w.Code != http.StatusBadRequest { + t.Errorf("expected status 400, got %d", w.Code) + } + + if diff := cmp.Diff(w.Body.String(), `{"error":"model is required"}`); diff != "" { + t.Errorf("mismatch (-got +want):\n%s", diff) + } + }) + + t.Run("missing model", func(t *testing.T) { + w := createRequest(t, s.ChatHandler, api.ChatRequest{}) + if w.Code != http.StatusBadRequest { + t.Errorf("expected status 400, got %d", w.Code) + } + + if diff := cmp.Diff(w.Body.String(), `{"error":"model is required"}`); diff != "" { + t.Errorf("mismatch (-got +want):\n%s", diff) + } + }) + + t.Run("missing capabilities", func(t *testing.T) { + w := createRequest(t, s.CreateModelHandler, api.CreateRequest{ + Name: "bert", + Modelfile: fmt.Sprintf("FROM %s", createBinFile(t, llm.KV{ + "general.architecture": "bert", + "bert.pooling_type": uint32(0), + }, []llm.Tensor{})), + Stream: &stream, + }) + + if w.Code != http.StatusOK { + t.Fatalf("expected status 200, got %d", w.Code) + } + + w = createRequest(t, s.ChatHandler, api.ChatRequest{ + Model: "bert", + }) + + if w.Code != http.StatusBadRequest { + t.Errorf("expected status 400, got %d", w.Code) + } + + if diff := cmp.Diff(w.Body.String(), `{"error":"\"bert\" does not support chat"}`); diff != "" { + t.Errorf("mismatch (-got +want):\n%s", diff) + } + }) + + t.Run("load model", func(t *testing.T) { + w := createRequest(t, s.ChatHandler, api.ChatRequest{ + Model: "test", + }) + + if w.Code != http.StatusOK { + t.Errorf("expected status 200, got %d", w.Code) + } + + var actual api.ChatResponse + if err := json.NewDecoder(w.Body).Decode(&actual); err != nil { + t.Fatal(err) + } + + if actual.Model != "test" { + t.Errorf("expected model test, got %s", actual.Model) + } + + if !actual.Done { + t.Errorf("expected done true, got false") + } + + if actual.DoneReason != "load" { + t.Errorf("expected done reason load, got %s", actual.DoneReason) + } + }) + + checkChatResponse := func(t *testing.T, body io.Reader, model, content string) { + t.Helper() + + var actual api.ChatResponse + if err := json.NewDecoder(body).Decode(&actual); err != nil { + t.Fatal(err) + } + + if actual.Model != model { + t.Errorf("expected model test, got %s", actual.Model) + } + + if !actual.Done { + t.Errorf("expected done false, got true") + } + + if actual.DoneReason != "stop" { + t.Errorf("expected done reason stop, got %s", actual.DoneReason) + } + + if diff := cmp.Diff(actual.Message, api.Message{ + Role: "assistant", + Content: content, + }); diff != "" { + t.Errorf("mismatch (-got +want):\n%s", diff) + } + + if actual.PromptEvalCount == 0 { + t.Errorf("expected prompt eval count > 0, got 0") + } + + if actual.PromptEvalDuration == 0 { + t.Errorf("expected prompt eval duration > 0, got 0") + } + + if actual.EvalCount == 0 { + t.Errorf("expected eval count > 0, got 0") + } + + if actual.EvalDuration == 0 { + t.Errorf("expected eval duration > 0, got 0") + } + + if actual.LoadDuration == 0 { + t.Errorf("expected load duration > 0, got 0") + } + + if actual.TotalDuration == 0 { + t.Errorf("expected load duration > 0, got 0") + } + } + + mock.CompletionResponse.Content = "Hi!" + t.Run("messages", func(t *testing.T) { + w := createRequest(t, s.ChatHandler, api.ChatRequest{ + Model: "test", + Messages: []api.Message{ + {Role: "user", Content: "Hello!"}, + }, + Stream: &stream, + }) + + if w.Code != http.StatusOK { + t.Errorf("expected status 200, got %d", w.Code) + } + + if diff := cmp.Diff(mock.CompletionRequest.Prompt, "User: Hello! "); diff != "" { + t.Errorf("mismatch (-got +want):\n%s", diff) + } + + checkChatResponse(t, w.Body, "test", "Hi!") + }) + + w = createRequest(t, s.CreateModelHandler, api.CreateRequest{ + Model: "test-system", + Modelfile: "FROM test\nSYSTEM You are a helpful assistant.", + }) + + if w.Code != http.StatusOK { + t.Fatalf("expected status 200, got %d", w.Code) + } + + t.Run("messages with model system", func(t *testing.T) { + w := createRequest(t, s.ChatHandler, api.ChatRequest{ + Model: "test-system", + Messages: []api.Message{ + {Role: "user", Content: "Hello!"}, + }, + Stream: &stream, + }) + + if w.Code != http.StatusOK { + t.Errorf("expected status 200, got %d", w.Code) + } + + if diff := cmp.Diff(mock.CompletionRequest.Prompt, "System: You are a helpful assistant. User: Hello! "); diff != "" { + t.Errorf("mismatch (-got +want):\n%s", diff) + } + + checkChatResponse(t, w.Body, "test-system", "Hi!") + }) + + mock.CompletionResponse.Content = "Abra kadabra!" + t.Run("messages with system", func(t *testing.T) { + w := createRequest(t, s.ChatHandler, api.ChatRequest{ + Model: "test-system", + Messages: []api.Message{ + {Role: "system", Content: "You can perform magic tricks."}, + {Role: "user", Content: "Hello!"}, + }, + Stream: &stream, + }) + + if w.Code != http.StatusOK { + t.Errorf("expected status 200, got %d", w.Code) + } + + if diff := cmp.Diff(mock.CompletionRequest.Prompt, "System: You can perform magic tricks. User: Hello! "); diff != "" { + t.Errorf("mismatch (-got +want):\n%s", diff) + } + + checkChatResponse(t, w.Body, "test-system", "Abra kadabra!") + }) + + t.Run("messages with interleaved system", func(t *testing.T) { + w := createRequest(t, s.ChatHandler, api.ChatRequest{ + Model: "test-system", + Messages: []api.Message{ + {Role: "user", Content: "Hello!"}, + {Role: "assistant", Content: "I can help you with that."}, + {Role: "system", Content: "You can perform magic tricks."}, + {Role: "user", Content: "Help me write tests."}, + }, + Stream: &stream, + }) + + if w.Code != http.StatusOK { + t.Errorf("expected status 200, got %d", w.Code) + } + + if diff := cmp.Diff(mock.CompletionRequest.Prompt, "System: You are a helpful assistant. User: Hello! Assistant: I can help you with that. System: You can perform magic tricks. User: Help me write tests. "); diff != "" { + t.Errorf("mismatch (-got +want):\n%s", diff) + } + + checkChatResponse(t, w.Body, "test-system", "Abra kadabra!") + }) +} + +func TestGenerate(t *testing.T) { + gin.SetMode(gin.TestMode) + + mock := mockRunner{ + CompletionResponse: llm.CompletionResponse{ + Done: true, + DoneReason: "stop", + PromptEvalCount: 1, + PromptEvalDuration: 1, + EvalCount: 1, + EvalDuration: 1, + }, + } + + s := Server{ + sched: &Scheduler{ + pendingReqCh: make(chan *LlmRequest, 1), + finishedReqCh: make(chan *LlmRequest, 1), + expiredCh: make(chan *runnerRef, 1), + unloadedCh: make(chan any, 1), + loaded: make(map[string]*runnerRef), + newServerFn: newMockServer(&mock), + getGpuFn: gpu.GetGPUInfo, + getCpuFn: gpu.GetCPUInfo, + reschedDelay: 250 * time.Millisecond, + loadFn: func(req *LlmRequest, ggml *llm.GGML, gpus gpu.GpuInfoList, numParallel int) { + req.successCh <- &runnerRef{ + llama: &mock, + } + }, + }, + } + + go s.sched.Run(context.TODO()) + + w := createRequest(t, s.CreateModelHandler, api.CreateRequest{ + Name: "test", + Modelfile: fmt.Sprintf(`FROM %s + TEMPLATE """ +{{- if .System }}System: {{ .System }} {{ end }} +{{- if .Prompt }}User: {{ .Prompt }} {{ end }} +{{- if .Response }}Assistant: {{ .Response }} {{ end }}""" +`, createBinFile(t, llm.KV{ + "general.architecture": "llama", + "llama.block_count": uint32(1), + "llama.context_length": uint32(8192), + "llama.embedding_length": uint32(4096), + "llama.attention.head_count": uint32(32), + "llama.attention.head_count_kv": uint32(8), + "tokenizer.ggml.tokens": []string{""}, + "tokenizer.ggml.scores": []float32{0}, + "tokenizer.ggml.token_type": []int32{0}, + }, []llm.Tensor{ + {Name: "token_embd.weight", Shape: []uint64{1}, WriterTo: bytes.NewReader(make([]byte, 4))}, + {Name: "blk.0.attn_norm.weight", Shape: []uint64{1}, WriterTo: bytes.NewReader(make([]byte, 4))}, + {Name: "blk.0.ffn_down.weight", Shape: []uint64{1}, WriterTo: bytes.NewReader(make([]byte, 4))}, + {Name: "blk.0.ffn_gate.weight", Shape: []uint64{1}, WriterTo: bytes.NewReader(make([]byte, 4))}, + {Name: "blk.0.ffn_up.weight", Shape: []uint64{1}, WriterTo: bytes.NewReader(make([]byte, 4))}, + {Name: "blk.0.ffn_norm.weight", Shape: []uint64{1}, WriterTo: bytes.NewReader(make([]byte, 4))}, + {Name: "blk.0.attn_k.weight", Shape: []uint64{1}, WriterTo: bytes.NewReader(make([]byte, 4))}, + {Name: "blk.0.attn_output.weight", Shape: []uint64{1}, WriterTo: bytes.NewReader(make([]byte, 4))}, + {Name: "blk.0.attn_q.weight", Shape: []uint64{1}, WriterTo: bytes.NewReader(make([]byte, 4))}, + {Name: "blk.0.attn_v.weight", Shape: []uint64{1}, WriterTo: bytes.NewReader(make([]byte, 4))}, + {Name: "output.weight", Shape: []uint64{1}, WriterTo: bytes.NewReader(make([]byte, 4))}, + })), + Stream: &stream, + }) + + if w.Code != http.StatusOK { + t.Fatalf("expected status 200, got %d", w.Code) + } + + t.Run("missing body", func(t *testing.T) { + w := createRequest(t, s.GenerateHandler, nil) + if w.Code != http.StatusBadRequest { + t.Errorf("expected status 400, got %d", w.Code) + } + + if diff := cmp.Diff(w.Body.String(), `{"error":"model is required"}`); diff != "" { + t.Errorf("mismatch (-got +want):\n%s", diff) + } + }) + + t.Run("missing model", func(t *testing.T) { + w := createRequest(t, s.GenerateHandler, api.GenerateRequest{}) + if w.Code != http.StatusBadRequest { + t.Errorf("expected status 400, got %d", w.Code) + } + + if diff := cmp.Diff(w.Body.String(), `{"error":"model is required"}`); diff != "" { + t.Errorf("mismatch (-got +want):\n%s", diff) + } + }) + + t.Run("missing capabilities", func(t *testing.T) { + w := createRequest(t, s.CreateModelHandler, api.CreateRequest{ + Name: "bert", + Modelfile: fmt.Sprintf("FROM %s", createBinFile(t, llm.KV{ + "general.architecture": "bert", + "bert.pooling_type": uint32(0), + }, []llm.Tensor{})), + Stream: &stream, + }) + + if w.Code != http.StatusOK { + t.Fatalf("expected status 200, got %d", w.Code) + } + + w = createRequest(t, s.GenerateHandler, api.GenerateRequest{ + Model: "bert", + }) + + if w.Code != http.StatusBadRequest { + t.Errorf("expected status 400, got %d", w.Code) + } + + if diff := cmp.Diff(w.Body.String(), `{"error":"\"bert\" does not support generate"}`); diff != "" { + t.Errorf("mismatch (-got +want):\n%s", diff) + } + }) + + t.Run("load model", func(t *testing.T) { + w := createRequest(t, s.GenerateHandler, api.GenerateRequest{ + Model: "test", + }) + + if w.Code != http.StatusOK { + t.Errorf("expected status 200, got %d", w.Code) + } + + var actual api.GenerateResponse + if err := json.NewDecoder(w.Body).Decode(&actual); err != nil { + t.Fatal(err) + } + + if actual.Model != "test" { + t.Errorf("expected model test, got %s", actual.Model) + } + + if !actual.Done { + t.Errorf("expected done true, got false") + } + + if actual.DoneReason != "load" { + t.Errorf("expected done reason load, got %s", actual.DoneReason) + } + }) + + checkGenerateResponse := func(t *testing.T, body io.Reader, model, content string) { + t.Helper() + + var actual api.GenerateResponse + if err := json.NewDecoder(body).Decode(&actual); err != nil { + t.Fatal(err) + } + + if actual.Model != model { + t.Errorf("expected model test, got %s", actual.Model) + } + + if !actual.Done { + t.Errorf("expected done false, got true") + } + + if actual.DoneReason != "stop" { + t.Errorf("expected done reason stop, got %s", actual.DoneReason) + } + + if actual.Response != content { + t.Errorf("expected response %s, got %s", content, actual.Response) + } + + if actual.Context == nil { + t.Errorf("expected context not nil") + } + + if actual.PromptEvalCount == 0 { + t.Errorf("expected prompt eval count > 0, got 0") + } + + if actual.PromptEvalDuration == 0 { + t.Errorf("expected prompt eval duration > 0, got 0") + } + + if actual.EvalCount == 0 { + t.Errorf("expected eval count > 0, got 0") + } + + if actual.EvalDuration == 0 { + t.Errorf("expected eval duration > 0, got 0") + } + + if actual.LoadDuration == 0 { + t.Errorf("expected load duration > 0, got 0") + } + + if actual.TotalDuration == 0 { + t.Errorf("expected load duration > 0, got 0") + } + } + + mock.CompletionResponse.Content = "Hi!" + t.Run("prompt", func(t *testing.T) { + w := createRequest(t, s.GenerateHandler, api.GenerateRequest{ + Model: "test", + Prompt: "Hello!", + Stream: &stream, + }) + + if w.Code != http.StatusOK { + t.Errorf("expected status 200, got %d", w.Code) + } + + if diff := cmp.Diff(mock.CompletionRequest.Prompt, "User: Hello! "); diff != "" { + t.Errorf("mismatch (-got +want):\n%s", diff) + } + + checkGenerateResponse(t, w.Body, "test", "Hi!") + }) + + w = createRequest(t, s.CreateModelHandler, api.CreateRequest{ + Model: "test-system", + Modelfile: "FROM test\nSYSTEM You are a helpful assistant.", + }) + + if w.Code != http.StatusOK { + t.Fatalf("expected status 200, got %d", w.Code) + } + + t.Run("prompt with model system", func(t *testing.T) { + w := createRequest(t, s.GenerateHandler, api.GenerateRequest{ + Model: "test-system", + Prompt: "Hello!", + Stream: &stream, + }) + + if w.Code != http.StatusOK { + t.Errorf("expected status 200, got %d", w.Code) + } + + if diff := cmp.Diff(mock.CompletionRequest.Prompt, "System: You are a helpful assistant. User: Hello! "); diff != "" { + t.Errorf("mismatch (-got +want):\n%s", diff) + } + + checkGenerateResponse(t, w.Body, "test-system", "Hi!") + }) + + mock.CompletionResponse.Content = "Abra kadabra!" + t.Run("prompt with system", func(t *testing.T) { + w := createRequest(t, s.GenerateHandler, api.GenerateRequest{ + Model: "test-system", + Prompt: "Hello!", + System: "You can perform magic tricks.", + Stream: &stream, + }) + + if w.Code != http.StatusOK { + t.Errorf("expected status 200, got %d", w.Code) + } + + if diff := cmp.Diff(mock.CompletionRequest.Prompt, "System: You can perform magic tricks. User: Hello! "); diff != "" { + t.Errorf("mismatch (-got +want):\n%s", diff) + } + + checkGenerateResponse(t, w.Body, "test-system", "Abra kadabra!") + }) + + t.Run("prompt with template", func(t *testing.T) { + w := createRequest(t, s.GenerateHandler, api.GenerateRequest{ + Model: "test-system", + Prompt: "Help me write tests.", + System: "You can perform magic tricks.", + Template: `{{- if .System }}{{ .System }} {{ end }} +{{- if .Prompt }}### USER {{ .Prompt }} {{ end }} +{{- if .Response }}### ASSISTANT {{ .Response }} {{ end }}`, + Stream: &stream, + }) + + if w.Code != http.StatusOK { + t.Errorf("expected status 200, got %d", w.Code) + } + + if diff := cmp.Diff(mock.CompletionRequest.Prompt, "You can perform magic tricks. ### USER Help me write tests. "); diff != "" { + t.Errorf("mismatch (-got +want):\n%s", diff) + } + + checkGenerateResponse(t, w.Body, "test-system", "Abra kadabra!") + }) + + t.Run("raw", func(t *testing.T) { + w := createRequest(t, s.GenerateHandler, api.GenerateRequest{ + Model: "test-system", + Prompt: "Help me write tests.", + Raw: true, + Stream: &stream, + }) + + if w.Code != http.StatusOK { + t.Errorf("expected status 200, got %d", w.Code) + } + + if diff := cmp.Diff(mock.CompletionRequest.Prompt, "Help me write tests."); diff != "" { + t.Errorf("mismatch (-got +want):\n%s", diff) + } + }) +} diff --git a/server/routes_list_test.go b/server/routes_list_test.go index d04be9d63..c2d9c1137 100644 --- a/server/routes_list_test.go +++ b/server/routes_list_test.go @@ -7,11 +7,14 @@ import ( "slices" "testing" + "github.com/gin-gonic/gin" "github.com/ollama/ollama/api" "github.com/ollama/ollama/envconfig" ) func TestList(t *testing.T) { + gin.SetMode(gin.TestMode) + t.Setenv("OLLAMA_MODELS", t.TempDir()) envconfig.LoadConfig() From 4cb5d7decc08f4c7c136f81b2b5f1c74ebffbc62 Mon Sep 17 00:00:00 2001 From: Jeffrey Morgan Date: Tue, 16 Jul 2024 11:09:00 -0700 Subject: [PATCH 093/384] server: omit model system prompt if empty (#5717) --- server/routes.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/routes.go b/server/routes.go index 9712d8950..d0cbe6ccd 100644 --- a/server/routes.go +++ b/server/routes.go @@ -1310,7 +1310,7 @@ func (s *Server) ChatHandler(c *gin.Context) { return } - if req.Messages[0].Role != "system" { + if req.Messages[0].Role != "system" && m.System != "" { req.Messages = append([]api.Message{{Role: "system", Content: m.System}}, req.Messages...) } From 5afbb60fc452965a4a53f1e46816ea41298269c6 Mon Sep 17 00:00:00 2001 From: Michael Yang Date: Tue, 16 Jul 2024 09:38:46 -0700 Subject: [PATCH 094/384] fix unmarshal type errors --- server/model.go | 48 +++++++++++++++++--------------------------- server/model_test.go | 33 +++++++++++++++++++----------- 2 files changed, 39 insertions(+), 42 deletions(-) diff --git a/server/model.go b/server/model.go index be318db9c..9e22d63a5 100644 --- a/server/model.go +++ b/server/model.go @@ -327,7 +327,8 @@ func (m *Model) parseToolCalls(s string) ([]api.ToolCall, bool) { var kv map[string]string // execute the subtree with placeholders to identify the keys - if err := json.Unmarshal(b.Bytes(), &kv); err != nil { + // trim any commands that might exist in the template + if err := json.Unmarshal(bytes.TrimSuffix(b.Bytes(), []byte(",")), &kv); err != nil { return nil, false } @@ -342,35 +343,26 @@ func (m *Model) parseToolCalls(s string) ([]api.ToolCall, bool) { } } - var sm []map[string]any - decoder := json.NewDecoder(strings.NewReader(s)) - for { - // incrementally decode the JSON into a list of JSON objects - // skipping over any invalid tokens - if err := decoder.Decode(&sm); err != nil { - if errors.Is(err, io.EOF) { - break - } - - if errors.As(err, new(*json.SyntaxError)) { - r := decoder.Buffered() - if _, err := r.Read(make([]byte, decoder.InputOffset()+1)); err != nil { - break - } - - decoder = json.NewDecoder(r) - continue - } - + var objs []map[string]any + for offset := 0; offset < len(s); { + if err := json.NewDecoder(strings.NewReader(s[offset:])).Decode(&objs); errors.Is(err, io.EOF) { + break + } else if syntax := &(json.SyntaxError{}); errors.As(err, &syntax) { + // skip over any syntax errors + offset += int(syntax.Offset) + } else if unmarshalType := &(json.UnmarshalTypeError{}); errors.As(err, &unmarshalType) { + // skip over any unmarshalable types + offset += int(unmarshalType.Offset) + } else if err != nil { return nil, false + } else { + // break when an object is decoded + break } - - // break as soon as a valid object is decoded - break } var toolCalls []api.ToolCall - for _, kv := range sm { + for _, kv := range objs { call := api.ToolCall{ ID: uuid.New().String(), Type: "function", @@ -388,9 +380,5 @@ func (m *Model) parseToolCalls(s string) ([]api.ToolCall, bool) { toolCalls = append(toolCalls, call) } - if len(toolCalls) > 0 { - return toolCalls, true - } - - return nil, false + return toolCalls, len(toolCalls) > 0 } diff --git a/server/model_test.go b/server/model_test.go index 025781928..d39f28911 100644 --- a/server/model_test.go +++ b/server/model_test.go @@ -136,11 +136,16 @@ func TestExecuteWithTools(t *testing.T) { cases := []struct { model string output string + ok bool }{ - {"mistral", `[TOOL_CALLS] [{"name": "get_current_weather", "arguments": {"format":"fahrenheit","location":"San Francisco, CA"}},{"name": "get_current_weather", "arguments": {"format":"celsius","location":"Toronto, Canada"}}]`}, + {"mistral", `[TOOL_CALLS] [{"name": "get_current_weather", "arguments": {"format":"fahrenheit","location":"San Francisco, CA"}},{"name": "get_current_weather", "arguments": {"format":"celsius","location":"Toronto, Canada"}}]`, true}, {"mistral", `[TOOL_CALLS] [{"name": "get_current_weather", "arguments": {"format":"fahrenheit","location":"San Francisco, CA"}},{"name": "get_current_weather", "arguments": {"format":"celsius","location":"Toronto, Canada"}}] -The temperature in San Francisco, CA is 70°F and in Toronto, Canada is 20°C.`}, +The temperature in San Francisco, CA is 70°F and in Toronto, Canada is 20°C.`, true}, + {"mistral", `I'm not aware of that information. However, I can suggest searching for the weather using the "get_current_weather" function: + + [{"name": "get_current_weather", "arguments": {"format":"fahrenheit","location":"San Francisco, CA"}},{"name": "get_current_weather", "arguments": {"format":"celsius","location":"Toronto, Canada"}}]`, true}, + {"mistral", " The weather in San Francisco, CA is 70°F and in Toronto, Canada is 20°C.", false}, {"command-r-plus", "Action: ```json" + ` [ { @@ -158,8 +163,10 @@ The temperature in San Francisco, CA is 70°F and in Toronto, Canada is 20°C.`} } } ] -` + "```"}, - {"firefunction", ` functools[{"name": "get_current_weather", "arguments": {"format":"fahrenheit","location":"San Francisco, CA"}},{"name": "get_current_weather", "arguments": {"format":"celsius","location":"Toronto, Canada"}}]`}, +` + "```", true}, + {"command-r-plus", " The weather in San Francisco, CA is 70°F and in Toronto, Canada is 20°C.", false}, + {"firefunction", ` functools[{"name": "get_current_weather", "arguments": {"format":"fahrenheit","location":"San Francisco, CA"}},{"name": "get_current_weather", "arguments": {"format":"celsius","location":"Toronto, Canada"}}]`, true}, + {"firefunction", " The weather in San Francisco, CA is 70°F and in Toronto, Canada is 20°C.", false}, } var tools []api.Tool @@ -216,17 +223,19 @@ The temperature in San Francisco, CA is 70°F and in Toronto, Canada is 20°C.`} t.Run("parse", func(t *testing.T) { m := &Model{Template: tmpl} actual, ok := m.parseToolCalls(tt.output) - if !ok { - t.Fatal("failed to parse tool calls") + if ok != tt.ok { + t.Fatalf("expected %t, got %t", tt.ok, ok) } - for i := range actual { - // ID is randomly generated so clear it for comparison - actual[i].ID = "" - } + if tt.ok { + for i := range actual { + // ID is randomly generated so clear it for comparison + actual[i].ID = "" + } - if diff := cmp.Diff(actual, calls); diff != "" { - t.Errorf("mismatch (-got +want):\n%s", diff) + if diff := cmp.Diff(actual, calls); diff != "" { + t.Errorf("mismatch (-got +want):\n%s", diff) + } } }) }) From 987dbab0b063653b2be71060449c8add7b76cc6e Mon Sep 17 00:00:00 2001 From: royjhan <65097070+royjhan@users.noreply.github.com> Date: Tue, 16 Jul 2024 13:36:08 -0700 Subject: [PATCH 095/384] OpenAI: /v1/embeddings compatibility (#5285) * OpenAI v1 models * Empty List Testing * Add back envconfig * v1/models docs * Remove Docs * OpenAI batch embed compatibility * merge conflicts * integrate with api/embed * ep * merge conflicts * request tests * rm resp test * merge conflict * merge conflict * test fixes * test fn renaming * input validation for empty string --------- Co-authored-by: jmorganca --- openai/openai.go | 111 ++++++++++++++++++++++++++++++++++++++++++ openai/openai_test.go | 72 +++++++++++++++++++++++++++ server/routes.go | 1 + 3 files changed, 184 insertions(+) diff --git a/openai/openai.go b/openai/openai.go index b289d73e8..88bdaec1f 100644 --- a/openai/openai.go +++ b/openai/openai.go @@ -61,6 +61,11 @@ type ResponseFormat struct { Type string `json:"type"` } +type EmbedRequest struct { + Input any `json:"input"` + Model string `json:"model"` +} + type ChatCompletionRequest struct { Model string `json:"model"` Messages []Message `json:"messages"` @@ -134,11 +139,23 @@ type Model struct { OwnedBy string `json:"owned_by"` } +type Embedding struct { + Object string `json:"object"` + Embedding []float32 `json:"embedding"` + Index int `json:"index"` +} + type ListCompletion struct { Object string `json:"object"` Data []Model `json:"data"` } +type EmbeddingList struct { + Object string `json:"object"` + Data []Embedding `json:"data"` + Model string `json:"model"` +} + func NewError(code int, message string) ErrorResponse { var etype string switch code { @@ -262,6 +279,27 @@ func toListCompletion(r api.ListResponse) ListCompletion { } } +func toEmbeddingList(model string, r api.EmbedResponse) EmbeddingList { + if r.Embeddings != nil { + var data []Embedding + for i, e := range r.Embeddings { + data = append(data, Embedding{ + Object: "embedding", + Embedding: e, + Index: i, + }) + } + + return EmbeddingList{ + Object: "list", + Data: data, + Model: model, + } + } + + return EmbeddingList{} +} + func toModel(r api.ShowResponse, m string) Model { return Model{ Id: m, @@ -465,6 +503,11 @@ type RetrieveWriter struct { model string } +type EmbedWriter struct { + BaseWriter + model string +} + func (w *BaseWriter) writeError(code int, data []byte) (int, error) { var serr api.StatusError err := json.Unmarshal(data, &serr) @@ -630,6 +673,33 @@ func (w *RetrieveWriter) Write(data []byte) (int, error) { return w.writeResponse(data) } +func (w *EmbedWriter) writeResponse(data []byte) (int, error) { + var embedResponse api.EmbedResponse + err := json.Unmarshal(data, &embedResponse) + + if err != nil { + return 0, err + } + + w.ResponseWriter.Header().Set("Content-Type", "application/json") + err = json.NewEncoder(w.ResponseWriter).Encode(toEmbeddingList(w.model, embedResponse)) + + if err != nil { + return 0, err + } + + return len(data), nil +} + +func (w *EmbedWriter) Write(data []byte) (int, error) { + code := w.ResponseWriter.Status() + if code != http.StatusOK { + return w.writeError(code, data) + } + + return w.writeResponse(data) +} + func ListMiddleware() gin.HandlerFunc { return func(c *gin.Context) { w := &ListWriter{ @@ -693,6 +763,47 @@ func CompletionsMiddleware() gin.HandlerFunc { id: fmt.Sprintf("cmpl-%d", rand.Intn(999)), } + c.Writer = w + c.Next() + } +} + +func EmbeddingsMiddleware() gin.HandlerFunc { + return func(c *gin.Context) { + var req EmbedRequest + err := c.ShouldBindJSON(&req) + if err != nil { + c.AbortWithStatusJSON(http.StatusBadRequest, NewError(http.StatusBadRequest, err.Error())) + return + } + + if req.Input == "" { + req.Input = []string{""} + } + + if req.Input == nil { + c.AbortWithStatusJSON(http.StatusBadRequest, NewError(http.StatusBadRequest, "invalid input")) + return + } + + if v, ok := req.Input.([]any); ok && len(v) == 0 { + c.AbortWithStatusJSON(http.StatusBadRequest, NewError(http.StatusBadRequest, "invalid input")) + return + } + + var b bytes.Buffer + if err := json.NewEncoder(&b).Encode(api.EmbedRequest{Model: req.Model, Input: req.Input}); err != nil { + c.AbortWithStatusJSON(http.StatusInternalServerError, NewError(http.StatusInternalServerError, err.Error())) + return + } + + c.Request.Body = io.NopCloser(&b) + + w := &EmbedWriter{ + BaseWriter: BaseWriter{ResponseWriter: c.Writer}, + model: req.Model, + } + c.Writer = w c.Next() diff --git a/openai/openai_test.go b/openai/openai_test.go index 99f8baaf3..5fc22b887 100644 --- a/openai/openai_test.go +++ b/openai/openai_test.go @@ -161,6 +161,78 @@ func TestMiddlewareRequests(t *testing.T) { } }, }, + { + Name: "embed handler single input", + Method: http.MethodPost, + Path: "/api/embed", + Handler: EmbeddingsMiddleware, + Setup: func(t *testing.T, req *http.Request) { + body := EmbedRequest{ + Input: "Hello", + Model: "test-model", + } + + bodyBytes, _ := json.Marshal(body) + + req.Body = io.NopCloser(bytes.NewReader(bodyBytes)) + req.Header.Set("Content-Type", "application/json") + }, + Expected: func(t *testing.T, req *http.Request) { + var embedReq api.EmbedRequest + if err := json.NewDecoder(req.Body).Decode(&embedReq); err != nil { + t.Fatal(err) + } + + if embedReq.Input != "Hello" { + t.Fatalf("expected 'Hello', got %s", embedReq.Input) + } + + if embedReq.Model != "test-model" { + t.Fatalf("expected 'test-model', got %s", embedReq.Model) + } + }, + }, + { + Name: "embed handler batch input", + Method: http.MethodPost, + Path: "/api/embed", + Handler: EmbeddingsMiddleware, + Setup: func(t *testing.T, req *http.Request) { + body := EmbedRequest{ + Input: []string{"Hello", "World"}, + Model: "test-model", + } + + bodyBytes, _ := json.Marshal(body) + + req.Body = io.NopCloser(bytes.NewReader(bodyBytes)) + req.Header.Set("Content-Type", "application/json") + }, + Expected: func(t *testing.T, req *http.Request) { + var embedReq api.EmbedRequest + if err := json.NewDecoder(req.Body).Decode(&embedReq); err != nil { + t.Fatal(err) + } + + input, ok := embedReq.Input.([]any) + + if !ok { + t.Fatalf("expected input to be a list") + } + + if input[0].(string) != "Hello" { + t.Fatalf("expected 'Hello', got %s", input[0]) + } + + if input[1].(string) != "World" { + t.Fatalf("expected 'World', got %s", input[1]) + } + + if embedReq.Model != "test-model" { + t.Fatalf("expected 'test-model', got %s", embedReq.Model) + } + }, + }, } gin.SetMode(gin.TestMode) diff --git a/server/routes.go b/server/routes.go index d0cbe6ccd..d22a099a4 100644 --- a/server/routes.go +++ b/server/routes.go @@ -1064,6 +1064,7 @@ func (s *Server) GenerateRoutes() http.Handler { // Compatibility endpoints r.POST("/v1/chat/completions", openai.ChatMiddleware(), s.ChatHandler) r.POST("/v1/completions", openai.CompletionsMiddleware(), s.GenerateHandler) + r.POST("/v1/embeddings", openai.EmbeddingsMiddleware(), s.EmbedHandler) r.GET("/v1/models", openai.ListMiddleware(), s.ListModelsHandler) r.GET("/v1/models/:model", openai.RetrieveMiddleware(), s.ShowModelHandler) From 5a83f79afdf14b60008120d6fbd4fe94ba3f5241 Mon Sep 17 00:00:00 2001 From: Michael Yang Date: Tue, 16 Jul 2024 13:48:38 -0700 Subject: [PATCH 096/384] remove unneeded tool calls --- api/types.go | 2 -- server/model.go | 7 +------ server/model_test.go | 7 ------- 3 files changed, 1 insertion(+), 15 deletions(-) diff --git a/api/types.go b/api/types.go index e670d1149..b18ee2287 100644 --- a/api/types.go +++ b/api/types.go @@ -115,8 +115,6 @@ type Message struct { } type ToolCall struct { - ID string `json:"id"` - Type string `json:"type"` Function struct { Name string `json:"name"` Arguments map[string]any `json:"arguments"` diff --git a/server/model.go b/server/model.go index 9e22d63a5..de65d6b61 100644 --- a/server/model.go +++ b/server/model.go @@ -16,7 +16,6 @@ import ( "strings" "text/template/parse" - "github.com/google/uuid" "github.com/ollama/ollama/api" "github.com/ollama/ollama/convert" "github.com/ollama/ollama/llm" @@ -363,11 +362,7 @@ func (m *Model) parseToolCalls(s string) ([]api.ToolCall, bool) { var toolCalls []api.ToolCall for _, kv := range objs { - call := api.ToolCall{ - ID: uuid.New().String(), - Type: "function", - } - + var call api.ToolCall for k, v := range kv { switch k { case name: diff --git a/server/model_test.go b/server/model_test.go index d39f28911..2e9dad3dd 100644 --- a/server/model_test.go +++ b/server/model_test.go @@ -181,7 +181,6 @@ The temperature in San Francisco, CA is 70°F and in Toronto, Canada is 20°C.`, calls := []api.ToolCall{ { - Type: "function", Function: function{ Name: "get_current_weather", Arguments: map[string]any{ @@ -191,7 +190,6 @@ The temperature in San Francisco, CA is 70°F and in Toronto, Canada is 20°C.`, }, }, { - Type: "function", Function: function{ Name: "get_current_weather", Arguments: map[string]any{ @@ -228,11 +226,6 @@ The temperature in San Francisco, CA is 70°F and in Toronto, Canada is 20°C.`, } if tt.ok { - for i := range actual { - // ID is randomly generated so clear it for comparison - actual[i].ID = "" - } - if diff := cmp.Diff(actual, calls); diff != "" { t.Errorf("mismatch (-got +want):\n%s", diff) } From 97c20ede33ca67f8bf21e55ffe318831d83290d0 Mon Sep 17 00:00:00 2001 From: Thorsten Sommer Date: Tue, 16 Jul 2024 23:24:27 +0200 Subject: [PATCH 097/384] README: Added AI Studio to the list of UIs (#5721) * Added AI Studio to the list of UIs --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index eb5e85329..40f4aeded 100644 --- a/README.md +++ b/README.md @@ -294,6 +294,7 @@ See the [API documentation](./docs/api.md) for all endpoints. - [LLocal.in](https://github.com/kartikm7/llocal) (Easy to use Electron Desktop Client for Ollama) - [Ollama with Google Mesop](https://github.com/rapidarchitect/ollama_mesop/) (Mesop Chat Client implementation with Ollama) - [Kerlig AI](https://www.kerlig.com/) (AI writing assistant for macOS) +- [AI Studio](https://github.com/MindWorkAI/AI-Studio) ### Terminal From d290e87513664be8ca3120348614d124991ccb86 Mon Sep 17 00:00:00 2001 From: Michael Yang Date: Thu, 20 Jun 2024 19:13:36 -0700 Subject: [PATCH 098/384] add suffix support to generate endpoint this change is triggered by the presence of "suffix", particularly useful for code completion tasks --- api/types.go | 3 ++ server/images.go | 17 ++++++-- server/routes.go | 40 +++++++++++------- server/routes_generate_test.go | 77 ++++++++++++++++++++++++++++++---- template/template.go | 10 ++++- template/template_test.go | 35 ++++++++++++++++ 6 files changed, 155 insertions(+), 27 deletions(-) diff --git a/api/types.go b/api/types.go index e670d1149..3029fca8a 100644 --- a/api/types.go +++ b/api/types.go @@ -47,6 +47,9 @@ type GenerateRequest struct { // Prompt is the textual prompt to send to the model. Prompt string `json:"prompt"` + // Suffix is the text that comes after the inserted text. + Suffix string `json:"suffix"` + // System overrides the model's default system message/prompt. System string `json:"system"` diff --git a/server/images.go b/server/images.go index 1b87888ed..5e4e88583 100644 --- a/server/images.go +++ b/server/images.go @@ -34,13 +34,19 @@ import ( "github.com/ollama/ollama/version" ) -var errCapabilityCompletion = errors.New("completion") +var ( + errCapabilities = errors.New("does not support") + errCapabilityCompletion = errors.New("completion") + errCapabilityTools = errors.New("tools") + errCapabilityInsert = errors.New("insert") +) type Capability string const ( CapabilityCompletion = Capability("completion") CapabilityTools = Capability("tools") + CapabilityInsert = Capability("insert") ) type registryOptions struct { @@ -93,7 +99,12 @@ func (m *Model) CheckCapabilities(caps ...Capability) error { } case CapabilityTools: if !slices.Contains(m.Template.Vars(), "tools") { - errs = append(errs, errors.New("tools")) + errs = append(errs, errCapabilityTools) + } + case CapabilityInsert: + vars := m.Template.Vars() + if !slices.Contains(vars, "suffix") { + errs = append(errs, errCapabilityInsert) } default: slog.Error("unknown capability", "capability", cap) @@ -102,7 +113,7 @@ func (m *Model) CheckCapabilities(caps ...Capability) error { } if err := errors.Join(errs...); err != nil { - return fmt.Errorf("does not support %w", errors.Join(errs...)) + return fmt.Errorf("%w %w", errCapabilities, errors.Join(errs...)) } return nil diff --git a/server/routes.go b/server/routes.go index d22a099a4..c7f74fa40 100644 --- a/server/routes.go +++ b/server/routes.go @@ -122,6 +122,10 @@ func (s *Server) GenerateHandler(c *gin.Context) { } caps := []Capability{CapabilityCompletion} + if req.Suffix != "" { + caps = append(caps, CapabilityInsert) + } + r, m, opts, err := s.scheduleRunner(c.Request.Context(), req.Model, caps, req.Options, req.KeepAlive) if errors.Is(err, errCapabilityCompletion) { c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Sprintf("%q does not support generate", req.Model)}) @@ -150,19 +154,6 @@ func (s *Server) GenerateHandler(c *gin.Context) { prompt := req.Prompt if !req.Raw { - var msgs []api.Message - if req.System != "" { - msgs = append(msgs, api.Message{Role: "system", Content: req.System}) - } else if m.System != "" { - msgs = append(msgs, api.Message{Role: "system", Content: m.System}) - } - - for _, i := range images { - msgs = append(msgs, api.Message{Role: "user", Content: fmt.Sprintf("[img-%d]", i.ID)}) - } - - msgs = append(msgs, api.Message{Role: "user", Content: req.Prompt}) - tmpl := m.Template if req.Template != "" { tmpl, err = template.Parse(req.Template) @@ -183,7 +174,26 @@ func (s *Server) GenerateHandler(c *gin.Context) { b.WriteString(s) } - if err := tmpl.Execute(&b, template.Values{Messages: msgs}); err != nil { + var values template.Values + if req.Suffix != "" { + values.Prompt = prompt + values.Suffix = req.Suffix + } else { + var msgs []api.Message + if req.System != "" { + msgs = append(msgs, api.Message{Role: "system", Content: req.System}) + } else if m.System != "" { + msgs = append(msgs, api.Message{Role: "system", Content: m.System}) + } + + for _, i := range images { + msgs = append(msgs, api.Message{Role: "user", Content: fmt.Sprintf("[img-%d]", i.ID)}) + } + + values.Messages = append(msgs, api.Message{Role: "user", Content: req.Prompt}) + } + + if err := tmpl.Execute(&b, values); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } @@ -1394,7 +1404,7 @@ func (s *Server) ChatHandler(c *gin.Context) { func handleScheduleError(c *gin.Context, name string, err error) { switch { - case errors.Is(err, errRequired): + case errors.Is(err, errCapabilities), errors.Is(err, errRequired): c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) case errors.Is(err, context.Canceled): c.JSON(499, gin.H{"error": "request canceled"}) diff --git a/server/routes_generate_test.go b/server/routes_generate_test.go index 9d8993284..c914b3006 100644 --- a/server/routes_generate_test.go +++ b/server/routes_generate_test.go @@ -73,6 +73,8 @@ func TestGenerateChat(t *testing.T) { getCpuFn: gpu.GetCPUInfo, reschedDelay: 250 * time.Millisecond, loadFn: func(req *LlmRequest, ggml *llm.GGML, gpus gpu.GpuInfoList, numParallel int) { + // add 10ms delay to simulate loading + time.Sleep(10 * time.Millisecond) req.successCh <- &runnerRef{ llama: &mock, } @@ -83,7 +85,7 @@ func TestGenerateChat(t *testing.T) { go s.sched.Run(context.TODO()) w := createRequest(t, s.CreateModelHandler, api.CreateRequest{ - Name: "test", + Model: "test", Modelfile: fmt.Sprintf(`FROM %s TEMPLATE """ {{- if .System }}System: {{ .System }} {{ end }} @@ -141,9 +143,9 @@ func TestGenerateChat(t *testing.T) { } }) - t.Run("missing capabilities", func(t *testing.T) { + t.Run("missing capabilities chat", func(t *testing.T) { w := createRequest(t, s.CreateModelHandler, api.CreateRequest{ - Name: "bert", + Model: "bert", Modelfile: fmt.Sprintf("FROM %s", createBinFile(t, llm.KV{ "general.architecture": "bert", "bert.pooling_type": uint32(0), @@ -243,7 +245,7 @@ func TestGenerateChat(t *testing.T) { } if actual.TotalDuration == 0 { - t.Errorf("expected load duration > 0, got 0") + t.Errorf("expected total duration > 0, got 0") } } @@ -379,7 +381,7 @@ func TestGenerate(t *testing.T) { go s.sched.Run(context.TODO()) w := createRequest(t, s.CreateModelHandler, api.CreateRequest{ - Name: "test", + Model: "test", Modelfile: fmt.Sprintf(`FROM %s TEMPLATE """ {{- if .System }}System: {{ .System }} {{ end }} @@ -437,9 +439,9 @@ func TestGenerate(t *testing.T) { } }) - t.Run("missing capabilities", func(t *testing.T) { + t.Run("missing capabilities generate", func(t *testing.T) { w := createRequest(t, s.CreateModelHandler, api.CreateRequest{ - Name: "bert", + Model: "bert", Modelfile: fmt.Sprintf("FROM %s", createBinFile(t, llm.KV{ "general.architecture": "bert", "bert.pooling_type": uint32(0), @@ -464,6 +466,22 @@ func TestGenerate(t *testing.T) { } }) + t.Run("missing capabilities suffix", func(t *testing.T) { + w := createRequest(t, s.GenerateHandler, api.GenerateRequest{ + Model: "test", + Prompt: "def add(", + Suffix: " return c", + }) + + if w.Code != http.StatusBadRequest { + t.Errorf("expected status 400, got %d", w.Code) + } + + if diff := cmp.Diff(w.Body.String(), `{"error":"test does not support insert"}`); diff != "" { + t.Errorf("mismatch (-got +want):\n%s", diff) + } + }) + t.Run("load model", func(t *testing.T) { w := createRequest(t, s.GenerateHandler, api.GenerateRequest{ Model: "test", @@ -540,7 +558,7 @@ func TestGenerate(t *testing.T) { } if actual.TotalDuration == 0 { - t.Errorf("expected load duration > 0, got 0") + t.Errorf("expected total duration > 0, got 0") } } @@ -632,6 +650,49 @@ func TestGenerate(t *testing.T) { checkGenerateResponse(t, w.Body, "test-system", "Abra kadabra!") }) + w = createRequest(t, s.CreateModelHandler, api.CreateRequest{ + Model: "test-suffix", + Modelfile: `FROM test +TEMPLATE """{{- if .Suffix }}
 {{ .Prompt }} {{ .Suffix }} 
+{{- else }}{{ .Prompt }}
+{{- end }}"""`,
+	})
+
+	if w.Code != http.StatusOK {
+		t.Fatalf("expected status 200, got %d", w.Code)
+	}
+
+	t.Run("prompt with suffix", func(t *testing.T) {
+		w := createRequest(t, s.GenerateHandler, api.GenerateRequest{
+			Model:  "test-suffix",
+			Prompt: "def add(",
+			Suffix: "    return c",
+		})
+
+		if w.Code != http.StatusOK {
+			t.Errorf("expected status 200, got %d", w.Code)
+		}
+
+		if diff := cmp.Diff(mock.CompletionRequest.Prompt, "
 def add(     return c "); diff != "" {
+			t.Errorf("mismatch (-got +want):\n%s", diff)
+		}
+	})
+
+	t.Run("prompt without suffix", func(t *testing.T) {
+		w := createRequest(t, s.GenerateHandler, api.GenerateRequest{
+			Model:  "test-suffix",
+			Prompt: "def add(",
+		})
+
+		if w.Code != http.StatusOK {
+			t.Errorf("expected status 200, got %d", w.Code)
+		}
+
+		if diff := cmp.Diff(mock.CompletionRequest.Prompt, "def add("); diff != "" {
+			t.Errorf("mismatch (-got +want):\n%s", diff)
+		}
+	})
+
 	t.Run("raw", func(t *testing.T) {
 		w := createRequest(t, s.GenerateHandler, api.GenerateRequest{
 			Model:  "test-system",
diff --git a/template/template.go b/template/template.go
index 7cdb30ef1..5330c0fa9 100644
--- a/template/template.go
+++ b/template/template.go
@@ -151,6 +151,8 @@ func (t *Template) Vars() []string {
 type Values struct {
 	Messages []api.Message
 	Tools    []api.Tool
+	Prompt   string
+	Suffix   string
 
 	// forceLegacy is a flag used to test compatibility with legacy templates
 	forceLegacy bool
@@ -204,7 +206,13 @@ func (t *Template) Subtree(fn func(parse.Node) bool) *template.Template {
 
 func (t *Template) Execute(w io.Writer, v Values) error {
 	system, messages := collate(v.Messages)
-	if !v.forceLegacy && slices.Contains(t.Vars(), "messages") {
+	if v.Prompt != "" && v.Suffix != "" {
+		return t.Template.Execute(w, map[string]any{
+			"Prompt":   v.Prompt,
+			"Suffix":   v.Suffix,
+			"Response": "",
+		})
+	} else if !v.forceLegacy && slices.Contains(t.Vars(), "messages") {
 		return t.Template.Execute(w, map[string]any{
 			"System":   system,
 			"Messages": messages,
diff --git a/template/template_test.go b/template/template_test.go
index c678f1b12..ae0db80b9 100644
--- a/template/template_test.go
+++ b/template/template_test.go
@@ -359,3 +359,38 @@ Answer: `,
 		})
 	}
 }
+
+func TestExecuteWithSuffix(t *testing.T) {
+	tmpl, err := Parse(`{{- if .Suffix }}
 {{ .Prompt }} {{ .Suffix }} 
+{{- else }}{{ .Prompt }}
+{{- end }}`)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	cases := []struct {
+		name   string
+		values Values
+		expect string
+	}{
+		{
+			"message", Values{Messages: []api.Message{{Role: "user", Content: "hello"}}}, "hello",
+		},
+		{
+			"prompt suffix", Values{Prompt: "def add(", Suffix: "return x"}, "
 def add( return x ",
+		},
+	}
+
+	for _, tt := range cases {
+		t.Run(tt.name, func(t *testing.T) {
+			var b bytes.Buffer
+			if err := tmpl.Execute(&b, tt.values); err != nil {
+				t.Fatal(err)
+			}
+
+			if diff := cmp.Diff(b.String(), tt.expect); diff != "" {
+				t.Errorf("mismatch (-got +want):\n%s", diff)
+			}
+		})
+	}
+}

From c279f96371575484d212ab069db96ee38dddceb1 Mon Sep 17 00:00:00 2001
From: Michael Yang 
Date: Tue, 16 Jul 2024 14:51:19 -0700
Subject: [PATCH 099/384] remove ToolCall from GenerateResponse

---
 api/types.go     | 3 ---
 server/routes.go | 5 -----
 2 files changed, 8 deletions(-)

diff --git a/api/types.go b/api/types.go
index c3f0bae00..e687b8a47 100644
--- a/api/types.go
+++ b/api/types.go
@@ -405,9 +405,6 @@ type GenerateResponse struct {
 	// Response is the textual response itself.
 	Response string `json:"response"`
 
-	// ToolCalls is the list of tools the model wants to call
-	ToolCalls []ToolCall `json:"tool_calls,omitempty"`
-
 	// Done specifies if the response is complete.
 	Done bool `json:"done"`
 
diff --git a/server/routes.go b/server/routes.go
index c7f74fa40..b4a8b4acc 100644
--- a/server/routes.go
+++ b/server/routes.go
@@ -275,11 +275,6 @@ func (s *Server) GenerateHandler(c *gin.Context) {
 		}
 
 		r.Response = sb.String()
-		if toolCalls, ok := m.parseToolCalls(sb.String()); ok {
-			r.ToolCalls = toolCalls
-			r.Response = ""
-		}
-
 		c.JSON(http.StatusOK, r)
 		return
 	}

From 0d41623b52b77725624a9f4dbc4c0f9a356a9021 Mon Sep 17 00:00:00 2001
From: royjhan <65097070+royjhan@users.noreply.github.com>
Date: Tue, 16 Jul 2024 20:50:14 -0700
Subject: [PATCH 100/384] OpenAI: Add Suffix to `v1/completions` (#5611)

* add suffix

* remove todo

* remove TODO

* add to test

* rm outdated prompt tokens info md

* fix test

* fix test
---
 docs/openai.md        | 4 ----
 openai/openai.go      | 4 ++--
 openai/openai_test.go | 5 +++++
 3 files changed, 7 insertions(+), 6 deletions(-)

diff --git a/docs/openai.md b/docs/openai.md
index 9dda05c3a..248ba74ae 100644
--- a/docs/openai.md
+++ b/docs/openai.md
@@ -103,10 +103,6 @@ curl http://localhost:11434/v1/chat/completions \
 - [ ] `user`
 - [ ] `n`
 
-#### Notes
-
-- `usage.prompt_tokens` will be 0 for completions where prompt evaluation is cached
-
 ## Models
 
 Before using a model, pull it locally `ollama pull`:
diff --git a/openai/openai.go b/openai/openai.go
index 88bdaec1f..81e4011d9 100644
--- a/openai/openai.go
+++ b/openai/openai.go
@@ -111,6 +111,7 @@ type CompletionRequest struct {
 	Stream           bool     `json:"stream"`
 	Temperature      *float32 `json:"temperature"`
 	TopP             float32  `json:"top_p"`
+	Suffix           string   `json:"suffix"`
 }
 
 type Completion struct {
@@ -188,7 +189,6 @@ func toChatCompletion(id string, r api.ChatResponse) ChatCompletion {
 			}(r.DoneReason),
 		}},
 		Usage: Usage{
-			// TODO: ollama returns 0 for prompt eval if the prompt was cached, but openai returns the actual count
 			PromptTokens:     r.PromptEvalCount,
 			CompletionTokens: r.EvalCount,
 			TotalTokens:      r.PromptEvalCount + r.EvalCount,
@@ -234,7 +234,6 @@ func toCompletion(id string, r api.GenerateResponse) Completion {
 			}(r.DoneReason),
 		}},
 		Usage: Usage{
-			// TODO: ollama returns 0 for prompt eval if the prompt was cached, but openai returns the actual count
 			PromptTokens:     r.PromptEvalCount,
 			CompletionTokens: r.EvalCount,
 			TotalTokens:      r.PromptEvalCount + r.EvalCount,
@@ -475,6 +474,7 @@ func fromCompleteRequest(r CompletionRequest) (api.GenerateRequest, error) {
 		Prompt:  r.Prompt,
 		Options: options,
 		Stream:  &r.Stream,
+		Suffix:  r.Suffix,
 	}, nil
 }
 
diff --git a/openai/openai_test.go b/openai/openai_test.go
index 5fc22b887..046ee69cb 100644
--- a/openai/openai_test.go
+++ b/openai/openai_test.go
@@ -85,6 +85,7 @@ func TestMiddlewareRequests(t *testing.T) {
 					Prompt:      "Hello",
 					Temperature: &temp,
 					Stop:        []string{"\n", "stop"},
+					Suffix:      "suffix",
 				}
 
 				bodyBytes, _ := json.Marshal(body)
@@ -115,6 +116,10 @@ func TestMiddlewareRequests(t *testing.T) {
 				if stopTokens[0] != "\n" || stopTokens[1] != "stop" {
 					t.Fatalf("expected ['\\n', 'stop'], got %v", stopTokens)
 				}
+
+				if genReq.Suffix != "suffix" {
+					t.Fatalf("expected 'suffix', got %s", genReq.Suffix)
+				}
 			},
 		},
 		{

From 154f6f45d4acd4ea1f2e35cac3b90eb6faeea6bd Mon Sep 17 00:00:00 2001
From: royjhan <65097070+royjhan@users.noreply.github.com>
Date: Tue, 16 Jul 2024 20:52:59 -0700
Subject: [PATCH 101/384] OpenAI: Support Tools (#5614)

* reopen pr

* tools

* remove tc from stream for now

* ID and Function

* openai expects arguments to be a string (#5739)

* mutually exclusive content and tool calls

* clean up

---------

Co-authored-by: Jeffrey Morgan 
---
 openai/openai.go | 57 ++++++++++++++++++++++++++++++++++++++++++++----
 1 file changed, 53 insertions(+), 4 deletions(-)

diff --git a/openai/openai.go b/openai/openai.go
index 81e4011d9..01864e480 100644
--- a/openai/openai.go
+++ b/openai/openai.go
@@ -7,6 +7,7 @@ import (
 	"encoding/json"
 	"fmt"
 	"io"
+	"log/slog"
 	"math/rand"
 	"net/http"
 	"strings"
@@ -29,8 +30,9 @@ type ErrorResponse struct {
 }
 
 type Message struct {
-	Role    string `json:"role"`
-	Content any    `json:"content"`
+	Role      string     `json:"role"`
+	Content   any        `json:"content"`
+	ToolCalls []ToolCall `json:"tool_calls,omitempty"`
 }
 
 type Choice struct {
@@ -78,6 +80,7 @@ type ChatCompletionRequest struct {
 	PresencePenalty  *float64        `json:"presence_penalty_penalty"`
 	TopP             *float64        `json:"top_p"`
 	ResponseFormat   *ResponseFormat `json:"response_format"`
+	Tools            []api.Tool      `json:"tools"`
 }
 
 type ChatCompletion struct {
@@ -133,6 +136,15 @@ type CompletionChunk struct {
 	SystemFingerprint string                `json:"system_fingerprint"`
 }
 
+type ToolCall struct {
+	ID       string `json:"id"`
+	Type     string `json:"type"`
+	Function struct {
+		Name      string `json:"name"`
+		Arguments string `json:"arguments"`
+	} `json:"function"`
+}
+
 type Model struct {
 	Id      string `json:"id"`
 	Object  string `json:"object"`
@@ -171,7 +183,31 @@ func NewError(code int, message string) ErrorResponse {
 	return ErrorResponse{Error{Type: etype, Message: message}}
 }
 
+func toolCallId() string {
+	const letterBytes = "abcdefghijklmnopqrstuvwxyz0123456789"
+	b := make([]byte, 8)
+	for i := range b {
+		b[i] = letterBytes[rand.Intn(len(letterBytes))]
+	}
+	return "call_" + strings.ToLower(string(b))
+}
+
 func toChatCompletion(id string, r api.ChatResponse) ChatCompletion {
+	toolCalls := make([]ToolCall, len(r.Message.ToolCalls))
+	for i, tc := range r.Message.ToolCalls {
+		toolCalls[i].ID = toolCallId()
+		toolCalls[i].Type = "function"
+		toolCalls[i].Function.Name = tc.Function.Name
+
+		args, err := json.Marshal(tc.Function.Arguments)
+		if err != nil {
+			slog.Error("could not marshall function arguments to json", "error", err)
+			continue
+		}
+
+		toolCalls[i].Function.Arguments = string(args)
+	}
+
 	return ChatCompletion{
 		Id:                id,
 		Object:            "chat.completion",
@@ -180,7 +216,7 @@ func toChatCompletion(id string, r api.ChatResponse) ChatCompletion {
 		SystemFingerprint: "fp_ollama",
 		Choices: []Choice{{
 			Index:   0,
-			Message: Message{Role: r.Message.Role, Content: r.Message.Content},
+			Message: Message{Role: r.Message.Role, Content: r.Message.Content, ToolCalls: toolCalls},
 			FinishReason: func(reason string) *string {
 				if len(reason) > 0 {
 					return &reason
@@ -366,7 +402,19 @@ func fromChatRequest(r ChatCompletionRequest) (*api.ChatRequest, error) {
 			}
 			messages = append(messages, message)
 		default:
-			return nil, fmt.Errorf("invalid message content type: %T", content)
+			if msg.ToolCalls == nil {
+				return nil, fmt.Errorf("invalid message content type: %T", content)
+			}
+
+			toolCalls := make([]api.ToolCall, len(msg.ToolCalls))
+			for i, tc := range msg.ToolCalls {
+				toolCalls[i].Function.Name = tc.Function.Name
+				err := json.Unmarshal([]byte(tc.Function.Arguments), &toolCalls[i].Function.Arguments)
+				if err != nil {
+					return nil, fmt.Errorf("invalid tool call arguments")
+				}
+			}
+			messages = append(messages, api.Message{Role: msg.Role, ToolCalls: toolCalls})
 		}
 	}
 
@@ -424,6 +472,7 @@ func fromChatRequest(r ChatCompletionRequest) (*api.ChatRequest, error) {
 		Format:   format,
 		Options:  options,
 		Stream:   &r.Stream,
+		Tools:    r.Tools,
 	}, nil
 }
 

From d281a6e6036cf122fc9828ca93c58d7d3d57502f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?P=C3=A1kozdi=20Gy=C3=B6rgy?= 
Date: Wed, 17 Jul 2024 19:24:44 +0200
Subject: [PATCH 102/384] add sidellama link (#5702)

---
 README.md | 1 +
 1 file changed, 1 insertion(+)

diff --git a/README.md b/README.md
index 40f4aeded..b96f4c161 100644
--- a/README.md
+++ b/README.md
@@ -295,6 +295,7 @@ See the [API documentation](./docs/api.md) for all endpoints.
 - [Ollama with Google Mesop](https://github.com/rapidarchitect/ollama_mesop/) (Mesop Chat Client implementation with Ollama)
 - [Kerlig AI](https://www.kerlig.com/) (AI writing assistant for macOS)
 - [AI Studio](https://github.com/MindWorkAI/AI-Studio)
+- [Sidellama](https://github.com/gyopak/sidellama) (browser-based LLM client)
 
 ### Terminal
 

From 5b82960df840a8bd545b9a60a1b69c089e0e24f1 Mon Sep 17 00:00:00 2001
From: Michael Yang 
Date: Wed, 17 Jul 2024 10:39:22 -0700
Subject: [PATCH 103/384] stub response (#5750)

---
 template/template.go | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)

diff --git a/template/template.go b/template/template.go
index 5330c0fa9..85b4d21a3 100644
--- a/template/template.go
+++ b/template/template.go
@@ -217,6 +217,7 @@ func (t *Template) Execute(w io.Writer, v Values) error {
 			"System":   system,
 			"Messages": messages,
 			"Tools":    v.Tools,
+			"Response": "",
 		})
 	}
 
@@ -270,8 +271,9 @@ func (t *Template) Execute(w io.Writer, v Values) error {
 
 	tree := parse.Tree{Root: nodes.(*parse.ListNode)}
 	if err := template.Must(template.New("").AddParseTree("", &tree)).Execute(&b, map[string]any{
-		"System": system,
-		"Prompt": prompt,
+		"System":   system,
+		"Prompt":   prompt,
+		"Response": "",
 	}); err != nil {
 		return err
 	}

From 5fd698812655fb87e31262072f8a3e6a34b438a9 Mon Sep 17 00:00:00 2001
From: Michael Yang 
Date: Wed, 17 Jul 2024 11:02:36 -0700
Subject: [PATCH 104/384] parse tool call as individual objects

---
 server/model.go                               |  8 ++--
 server/model_test.go                          |  4 ++
 .../tools/llama3-groq-tool-use.gotmpl         | 43 +++++++++++++++++++
 .../testdata/tools/llama3-groq-tool-use.out   | 24 +++++++++++
 4 files changed, 76 insertions(+), 3 deletions(-)
 create mode 100644 server/testdata/tools/llama3-groq-tool-use.gotmpl
 create mode 100644 server/testdata/tools/llama3-groq-tool-use.out

diff --git a/server/model.go b/server/model.go
index de65d6b61..e5d6179bf 100644
--- a/server/model.go
+++ b/server/model.go
@@ -344,7 +344,9 @@ func (m *Model) parseToolCalls(s string) ([]api.ToolCall, bool) {
 
 	var objs []map[string]any
 	for offset := 0; offset < len(s); {
-		if err := json.NewDecoder(strings.NewReader(s[offset:])).Decode(&objs); errors.Is(err, io.EOF) {
+		var obj map[string]any
+		decoder := json.NewDecoder(strings.NewReader(s[offset:]))
+		if err := decoder.Decode(&obj); errors.Is(err, io.EOF) {
 			break
 		} else if syntax := &(json.SyntaxError{}); errors.As(err, &syntax) {
 			// skip over any syntax errors
@@ -355,8 +357,8 @@ func (m *Model) parseToolCalls(s string) ([]api.ToolCall, bool) {
 		} else if err != nil {
 			return nil, false
 		} else {
-			// break when an object is decoded
-			break
+			offset += int(decoder.InputOffset())
+			objs = append(objs, obj)
 		}
 	}
 
diff --git a/server/model_test.go b/server/model_test.go
index 2e9dad3dd..f0382843a 100644
--- a/server/model_test.go
+++ b/server/model_test.go
@@ -167,6 +167,10 @@ The temperature in San Francisco, CA is 70°F and in Toronto, Canada is 20°C.`,
 		{"command-r-plus", " The weather in San Francisco, CA is 70°F and in Toronto, Canada is 20°C.", false},
 		{"firefunction", ` functools[{"name": "get_current_weather", "arguments": {"format":"fahrenheit","location":"San Francisco, CA"}},{"name": "get_current_weather", "arguments": {"format":"celsius","location":"Toronto, Canada"}}]`, true},
 		{"firefunction", " The weather in San Francisco, CA is 70°F and in Toronto, Canada is 20°C.", false},
+		{"llama3-groq-tool-use", `
+{"name": "get_current_weather", "arguments": {"format":"fahrenheit","location":"San Francisco, CA"}}
+{"name": "get_current_weather", "arguments": {"format":"celsius","location":"Toronto, Canada"}}
+`, true},
 	}
 
 	var tools []api.Tool
diff --git a/server/testdata/tools/llama3-groq-tool-use.gotmpl b/server/testdata/tools/llama3-groq-tool-use.gotmpl
new file mode 100644
index 000000000..e174f8a57
--- /dev/null
+++ b/server/testdata/tools/llama3-groq-tool-use.gotmpl
@@ -0,0 +1,43 @@
+{{- if .Messages }}
+{{- if or .System .Tools }}<|start_header_id|>system<|end_header_id|>
+
+{{ .System }}
+{{- if .Tools }} You are provided with function signatures within  XML tags. You may call one or more functions to assist with the user query. Don't make assumptions about what values to plug into functions. For each function call return a json object with function name and arguments within  XML tags as follows:
+
+{"name": ,"arguments": }
+
+
+Here are the available tools:
+
+{{- range .Tools }} {{ json .Function }}
+{{- end }} 
+{{- end }}
+{{- end }}<|eot_id|>
+{{- range .Messages }}
+{{- if ne .Role "system" }}<|start_header_id|>{{ .Role }}<|end_header_id|>
+
+{{ if eq .Role "user" }}{{ .Content }}
+{{- else if eq .Role "assistant" }}
+{{- if .Content }}{{ .Content }}
+{{- else if .ToolCalls }}
+{{ range .ToolCalls }}{"name": "{{ .Function.Name }}", "arguments": {{ json .Function.Arguments }}}
+{{- end }}
+
+{{- end }}
+{{- else if eq .Role "tool" }}
+{{ .Content }}
+
+{{- end }}<|eot_id|>
+{{- end }}
+{{- end }}<|start_header_id|>assistant<|end_header_id|>
+
+{{ else }}
+{{ if .System }}<|start_header_id|>system<|end_header_id|>
+
+{{ .System }}<|eot_id|>{{ end }}{{ if .Prompt }}<|start_header_id|>user<|end_header_id|>
+
+{{ .Prompt }}<|eot_id|>{{ end }}<|start_header_id|>assistant<|end_header_id|>
+
+{{ end }}{{ .Response }}
+{{- if .Response }}<|eot_id|>
+{{- end }}
\ No newline at end of file
diff --git a/server/testdata/tools/llama3-groq-tool-use.out b/server/testdata/tools/llama3-groq-tool-use.out
new file mode 100644
index 000000000..75a495582
--- /dev/null
+++ b/server/testdata/tools/llama3-groq-tool-use.out
@@ -0,0 +1,24 @@
+<|start_header_id|>system<|end_header_id|>
+
+You are a knowledgable assistant. You can answer questions and perform tasks. You are provided with function signatures within  XML tags. You may call one or more functions to assist with the user query. Don't make assumptions about what values to plug into functions. For each function call return a json object with function name and arguments within  XML tags as follows:
+
+{"name": ,"arguments": }
+
+
+Here are the available tools:
+ {"name":"get_current_weather","description":"Get the current weather","parameters":{"type":"object","required":["location","format"],"properties":{"format":{"type":"string","description":"The temperature unit to use. Infer this from the users location.","enum":["celsius","fahrenheit"]},"location":{"type":"string","description":"The city and state, e.g. San Francisco, CA"}}}} <|eot_id|><|start_header_id|>user<|end_header_id|>
+
+What's the weather like today in Paris?<|eot_id|><|start_header_id|>assistant<|end_header_id|>
+
+
+{"name": "get_current_weather", "arguments": {"format":"celsius","location":"Paris, France"}}
+<|eot_id|><|start_header_id|>tool<|end_header_id|>
+
+
+22
+<|eot_id|><|start_header_id|>assistant<|end_header_id|>
+
+The current temperature in Paris, France is 22 degrees Celsius.<|eot_id|><|start_header_id|>user<|end_header_id|>
+
+What's the weather like today in San Francisco and Toronto?<|eot_id|><|start_header_id|>assistant<|end_header_id|>
+

From f02f83660c2e6f0741932bb31a28b82950144dfc Mon Sep 17 00:00:00 2001
From: lreed 
Date: Wed, 17 Jul 2024 21:44:19 +0000
Subject: [PATCH 105/384] bump go version to 1.22.5 to fix security
 vulnerabilities

---
 Dockerfile | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/Dockerfile b/Dockerfile
index ca3934964..c8efdd8a2 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,4 +1,4 @@
-ARG GOLANG_VERSION=1.22.1
+ARG GOLANG_VERSION=1.22.5
 ARG CMAKE_VERSION=3.22.1
 # this CUDA_VERSION corresponds with the one specified in docs/gpu.md
 ARG CUDA_VERSION=11.3.1

From b2554455572b28c0e18423d6fe6896cf7137dbd6 Mon Sep 17 00:00:00 2001
From: Michael Yang 
Date: Wed, 17 Jul 2024 15:35:11 -0700
Subject: [PATCH 106/384] marshal json automatically for some template values
 (#5758)

---
 api/types.go                                  | 73 ++++++++++++-------
 server/model.go                               | 18 +++--
 server/model_test.go                          | 13 +---
 server/testdata/tools/command-r-plus.gotmpl   |  2 +-
 server/testdata/tools/firefunction.gotmpl     |  4 +-
 .../tools/llama3-groq-tool-use.gotmpl         |  4 +-
 server/testdata/tools/mistral.gotmpl          |  4 +-
 template/template.go                          |  6 +-
 8 files changed, 72 insertions(+), 52 deletions(-)

diff --git a/api/types.go b/api/types.go
index e687b8a47..c7e9dce3b 100644
--- a/api/types.go
+++ b/api/types.go
@@ -101,12 +101,19 @@ type ChatRequest struct {
 	KeepAlive *Duration `json:"keep_alive,omitempty"`
 
 	// Tools is an optional list of tools the model has access to.
-	Tools []Tool `json:"tools,omitempty"`
+	Tools `json:"tools,omitempty"`
 
 	// Options lists model-specific options.
 	Options map[string]interface{} `json:"options"`
 }
 
+type Tools []Tool
+
+func (t Tools) String() string {
+	bts, _ := json.Marshal(t)
+	return string(bts)
+}
+
 // Message is a single message in a chat sequence. The message contains the
 // role ("system", "user", or "assistant"), the content and an optional list
 // of images.
@@ -117,30 +124,6 @@ type Message struct {
 	ToolCalls []ToolCall  `json:"tool_calls,omitempty"`
 }
 
-type ToolCall struct {
-	Function struct {
-		Name      string         `json:"name"`
-		Arguments map[string]any `json:"arguments"`
-	} `json:"function"`
-}
-
-type Tool struct {
-	Type     string `json:"type"`
-	Function struct {
-		Name        string `json:"name"`
-		Description string `json:"description"`
-		Parameters  struct {
-			Type       string   `json:"type"`
-			Required   []string `json:"required"`
-			Properties map[string]struct {
-				Type        string   `json:"type"`
-				Description string   `json:"description"`
-				Enum        []string `json:"enum,omitempty"`
-			} `json:"properties"`
-		} `json:"parameters"`
-	} `json:"function"`
-}
-
 func (m *Message) UnmarshalJSON(b []byte) error {
 	type Alias Message
 	var a Alias
@@ -153,6 +136,46 @@ func (m *Message) UnmarshalJSON(b []byte) error {
 	return nil
 }
 
+type ToolCall struct {
+	Function ToolCallFunction `json:"function"`
+}
+
+type ToolCallFunction struct {
+	Name      string                    `json:"name"`
+	Arguments ToolCallFunctionArguments `json:"arguments"`
+}
+
+type ToolCallFunctionArguments map[string]any
+
+func (t *ToolCallFunctionArguments) String() string {
+	bts, _ := json.Marshal(t)
+	return string(bts)
+}
+
+type Tool struct {
+	Type     string       `json:"type"`
+	Function ToolFunction `json:"function"`
+}
+
+type ToolFunction struct {
+	Name        string `json:"name"`
+	Description string `json:"description"`
+	Parameters  struct {
+		Type       string   `json:"type"`
+		Required   []string `json:"required"`
+		Properties map[string]struct {
+			Type        string   `json:"type"`
+			Description string   `json:"description"`
+			Enum        []string `json:"enum,omitempty"`
+		} `json:"properties"`
+	} `json:"parameters"`
+}
+
+func (t *ToolFunction) String() string {
+	bts, _ := json.Marshal(t)
+	return string(bts)
+}
+
 // ChatResponse is the response returned by [Client.Chat]. Its fields are
 // similar to [GenerateResponse].
 type ChatResponse struct {
diff --git a/server/model.go b/server/model.go
index e5d6179bf..65231ab1f 100644
--- a/server/model.go
+++ b/server/model.go
@@ -311,12 +311,14 @@ func (m *Model) parseToolCalls(s string) ([]api.ToolCall, bool) {
 	}
 
 	var b bytes.Buffer
-	if err := tmpl.Execute(&b, map[string][]map[string]any{
+	if err := tmpl.Execute(&b, map[string][]api.ToolCall{
 		"ToolCalls": {
 			{
-				"Function": map[string]any{
-					"Name":      "@@name@@",
-					"Arguments": "@@arguments@@",
+				Function: api.ToolCallFunction{
+					Name: "@@name@@",
+					Arguments: api.ToolCallFunctionArguments{
+						"@@argument@@": 1,
+					},
 				},
 			},
 		},
@@ -324,7 +326,7 @@ func (m *Model) parseToolCalls(s string) ([]api.ToolCall, bool) {
 		return nil, false
 	}
 
-	var kv map[string]string
+	var kv map[string]any
 	// execute the subtree with placeholders to identify the keys
 	// trim any commands that might exist in the template
 	if err := json.Unmarshal(bytes.TrimSuffix(b.Bytes(), []byte(",")), &kv); err != nil {
@@ -334,10 +336,10 @@ func (m *Model) parseToolCalls(s string) ([]api.ToolCall, bool) {
 	// find the keys that correspond to the name and arguments fields
 	var name, arguments string
 	for k, v := range kv {
-		switch v {
-		case "@@name@@":
+		switch v.(type) {
+		case string:
 			name = k
-		case "@@arguments@@":
+		case map[string]any:
 			arguments = k
 		}
 	}
diff --git a/server/model_test.go b/server/model_test.go
index f0382843a..7c826b066 100644
--- a/server/model_test.go
+++ b/server/model_test.go
@@ -115,11 +115,6 @@ func TestExtractFromZipFile(t *testing.T) {
 	}
 }
 
-type function struct {
-	Name      string         `json:"name"`
-	Arguments map[string]any `json:"arguments"`
-}
-
 func readFile(t *testing.T, base, name string) *bytes.Buffer {
 	t.Helper()
 
@@ -185,18 +180,18 @@ The temperature in San Francisco, CA is 70°F and in Toronto, Canada is 20°C.`,
 
 	calls := []api.ToolCall{
 		{
-			Function: function{
+			Function: api.ToolCallFunction{
 				Name: "get_current_weather",
-				Arguments: map[string]any{
+				Arguments: api.ToolCallFunctionArguments{
 					"format":   "fahrenheit",
 					"location": "San Francisco, CA",
 				},
 			},
 		},
 		{
-			Function: function{
+			Function: api.ToolCallFunction{
 				Name: "get_current_weather",
-				Arguments: map[string]any{
+				Arguments: api.ToolCallFunctionArguments{
 					"format":   "celsius",
 					"location": "Toronto, Canada",
 				},
diff --git a/server/testdata/tools/command-r-plus.gotmpl b/server/testdata/tools/command-r-plus.gotmpl
index 088a4f0e5..f30124e37 100644
--- a/server/testdata/tools/command-r-plus.gotmpl
+++ b/server/testdata/tools/command-r-plus.gotmpl
@@ -46,7 +46,7 @@ Action: ```json
 {{- range .ToolCalls }}
     {
         "tool_name": "{{ .Function.Name }}",
-        "parameters": {{ json .Function.Arguments }}
+        "parameters": {{ .Function.Arguments }}
     }
 {{- end }}
 ]```
diff --git a/server/testdata/tools/firefunction.gotmpl b/server/testdata/tools/firefunction.gotmpl
index bca88b3bd..312be205c 100644
--- a/server/testdata/tools/firefunction.gotmpl
+++ b/server/testdata/tools/firefunction.gotmpl
@@ -17,7 +17,7 @@ If you decide to call functions:
 
 Available functions as JSON spec:
 {{- if .Tools }}
-{{ json .Tools }}
+{{ .Tools }}
 {{- end }}<|eot_id|>
 {{- end }}
 {{- range .Messages }}<|start_header_id|>
@@ -25,7 +25,7 @@ Available functions as JSON spec:
 {{- end }}<|end_header_id|>
 {{- if .Content }}{{ .Content }}
 {{- else if .ToolCalls }} functools[
-{{- range .ToolCalls }}{{ "{" }}"name": "{{ .Function.Name }}", "arguments": {{ json .Function.Arguments }}{{ "}" }}
+{{- range .ToolCalls }}{{ "{" }}"name": "{{ .Function.Name }}", "arguments": {{ .Function.Arguments }}{{ "}" }}
 {{- end }}]
 {{- end }}<|eot_id|>
 {{- end }}<|start_header_id|>assistant<|end_header_id|>
\ No newline at end of file
diff --git a/server/testdata/tools/llama3-groq-tool-use.gotmpl b/server/testdata/tools/llama3-groq-tool-use.gotmpl
index e174f8a57..45e9b462f 100644
--- a/server/testdata/tools/llama3-groq-tool-use.gotmpl
+++ b/server/testdata/tools/llama3-groq-tool-use.gotmpl
@@ -9,7 +9,7 @@
 
 Here are the available tools:
 
-{{- range .Tools }} {{ json .Function }}
+{{- range .Tools }} {{ .Function }}
 {{- end }} 
 {{- end }}
 {{- end }}<|eot_id|>
@@ -20,7 +20,7 @@ Here are the available tools:
 {{- else if eq .Role "assistant" }}
 {{- if .Content }}{{ .Content }}
 {{- else if .ToolCalls }}
-{{ range .ToolCalls }}{"name": "{{ .Function.Name }}", "arguments": {{ json .Function.Arguments }}}
+{{ range .ToolCalls }}{"name": "{{ .Function.Name }}", "arguments": {{ .Function.Arguments }}}
 {{- end }}
 
 {{- end }}
diff --git a/server/testdata/tools/mistral.gotmpl b/server/testdata/tools/mistral.gotmpl
index a98bc7ad6..b08d6c2c1 100644
--- a/server/testdata/tools/mistral.gotmpl
+++ b/server/testdata/tools/mistral.gotmpl
@@ -1,13 +1,13 @@
 {{- range $index, $_ := .Messages }}
 {{- if eq .Role "user" }}
-{{- if and (eq (len (slice $.Messages $index)) 1) $.Tools }}[AVAILABLE_TOOLS] {{ json $.Tools }}[/AVAILABLE_TOOLS]
+{{- if and (eq (len (slice $.Messages $index)) 1) $.Tools }}[AVAILABLE_TOOLS] {{ $.Tools }}[/AVAILABLE_TOOLS]
 {{- end }}[INST] {{ if and (eq (len (slice $.Messages $index)) 1) $.System }}{{ $.System }}
 
 {{ end }}{{ .Content }}[/INST]
 {{- else if eq .Role "assistant" }}
 {{- if .Content }} {{ .Content }}
 {{- else if .ToolCalls }}[TOOL_CALLS] [
-{{- range .ToolCalls }}{"name": "{{ .Function.Name }}", "arguments": {{ json .Function.Arguments }}}
+{{- range .ToolCalls }}{"name": "{{ .Function.Name }}", "arguments": {{ .Function.Arguments }}}
 {{- end }}]
 {{- end }}
 {{- else if eq .Role "tool" }}[TOOL_RESULTS] {"content": {{ .Content }}}[/TOOL_RESULTS]
diff --git a/template/template.go b/template/template.go
index 85b4d21a3..b5bfb16ce 100644
--- a/template/template.go
+++ b/template/template.go
@@ -150,9 +150,9 @@ func (t *Template) Vars() []string {
 
 type Values struct {
 	Messages []api.Message
-	Tools    []api.Tool
-	Prompt   string
-	Suffix   string
+	api.Tools
+	Prompt string
+	Suffix string
 
 	// forceLegacy is a flag used to test compatibility with legacy templates
 	forceLegacy bool

From 319fb1ce033921b70685abce0b2e06f52304fe74 Mon Sep 17 00:00:00 2001
From: Jeffrey Morgan 
Date: Thu, 18 Jul 2024 08:50:23 -0700
Subject: [PATCH 107/384] server: only parse tool calls if tools are provided
 (#5771)

* server: only parse tool calls if tools are provided

* still set `resp.Message.Content`
---
 server/routes.go | 9 ++++++---
 1 file changed, 6 insertions(+), 3 deletions(-)

diff --git a/server/routes.go b/server/routes.go
index b4a8b4acc..347d5221a 100644
--- a/server/routes.go
+++ b/server/routes.go
@@ -1385,9 +1385,12 @@ func (s *Server) ChatHandler(c *gin.Context) {
 		}
 
 		resp.Message.Content = sb.String()
-		if toolCalls, ok := m.parseToolCalls(sb.String()); ok {
-			resp.Message.ToolCalls = toolCalls
-			resp.Message.Content = ""
+
+		if len(req.Tools) > 0 {
+			if toolCalls, ok := m.parseToolCalls(sb.String()); ok {
+				resp.Message.ToolCalls = toolCalls
+				resp.Message.Content = ""
+			}
 		}
 
 		c.JSON(http.StatusOK, resp)

From 84e5721f3a2febbd8bc2600bedcaa6e67aaa9b49 Mon Sep 17 00:00:00 2001
From: Jeffrey Morgan 
Date: Thu, 18 Jul 2024 11:28:19 -0700
Subject: [PATCH 108/384] always provide content even if empty (#5778)

---
 api/types.go | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/api/types.go b/api/types.go
index c7e9dce3b..65a99c763 100644
--- a/api/types.go
+++ b/api/types.go
@@ -119,7 +119,7 @@ func (t Tools) String() string {
 // of images.
 type Message struct {
 	Role      string      `json:"role"`
-	Content   string      `json:"content,omitempty"`
+	Content   string      `json:"content"`
 	Images    []ImageData `json:"images,omitempty"`
 	ToolCalls []ToolCall  `json:"tool_calls,omitempty"`
 }

From 70b1010fa53095b4f8699b04375fa29f8c3e54b0 Mon Sep 17 00:00:00 2001
From: Jeffrey Morgan 
Date: Thu, 18 Jul 2024 11:44:57 -0700
Subject: [PATCH 109/384] server: check for empty tools array too (#5779)

---
 server/routes.go | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/server/routes.go b/server/routes.go
index 347d5221a..c33b71954 100644
--- a/server/routes.go
+++ b/server/routes.go
@@ -1290,7 +1290,7 @@ func (s *Server) ChatHandler(c *gin.Context) {
 	}
 
 	caps := []Capability{CapabilityCompletion}
-	if req.Tools != nil {
+	if len(req.Tools) > 0 {
 		caps = append(caps, CapabilityTools)
 	}
 

From 43606d6d6a0d5e4105692d6d1233cfaa96a562b0 Mon Sep 17 00:00:00 2001
From: Michael Yang 
Date: Thu, 18 Jul 2024 12:07:59 -0700
Subject: [PATCH 110/384] fix parsing tool calls

---
 server/model.go | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/server/model.go b/server/model.go
index 65231ab1f..a084dd8c8 100644
--- a/server/model.go
+++ b/server/model.go
@@ -348,7 +348,7 @@ func (m *Model) parseToolCalls(s string) ([]api.ToolCall, bool) {
 	for offset := 0; offset < len(s); {
 		var obj map[string]any
 		decoder := json.NewDecoder(strings.NewReader(s[offset:]))
-		if err := decoder.Decode(&obj); errors.Is(err, io.EOF) {
+		if err := decoder.Decode(&obj); errors.Is(err, io.EOF) || errors.Is(err, io.ErrUnexpectedEOF) {
 			break
 		} else if syntax := &(json.SyntaxError{}); errors.As(err, &syntax) {
 			// skip over any syntax errors
@@ -357,6 +357,7 @@ func (m *Model) parseToolCalls(s string) ([]api.ToolCall, bool) {
 			// skip over any unmarshalable types
 			offset += int(unmarshalType.Offset)
 		} else if err != nil {
+			slog.Error("parseToolCalls", "error", err)
 			return nil, false
 		} else {
 			offset += int(decoder.InputOffset())

From 51b2fd299cd568093ce796aef3e7e37ae656b02a Mon Sep 17 00:00:00 2001
From: royjhan <65097070+royjhan@users.noreply.github.com>
Date: Fri, 19 Jul 2024 11:19:20 -0700
Subject: [PATCH 111/384] adjust openai chat msg processing (#5729)

---
 openai/openai.go      | 7 +++----
 openai/openai_test.go | 8 ++++++--
 2 files changed, 9 insertions(+), 6 deletions(-)

diff --git a/openai/openai.go b/openai/openai.go
index 01864e480..93b632968 100644
--- a/openai/openai.go
+++ b/openai/openai.go
@@ -351,7 +351,6 @@ func fromChatRequest(r ChatCompletionRequest) (*api.ChatRequest, error) {
 		case string:
 			messages = append(messages, api.Message{Role: msg.Role, Content: content})
 		case []any:
-			message := api.Message{Role: msg.Role}
 			for _, c := range content {
 				data, ok := c.(map[string]any)
 				if !ok {
@@ -363,7 +362,7 @@ func fromChatRequest(r ChatCompletionRequest) (*api.ChatRequest, error) {
 					if !ok {
 						return nil, fmt.Errorf("invalid message format")
 					}
-					message.Content = text
+					messages = append(messages, api.Message{Role: msg.Role, Content: text})
 				case "image_url":
 					var url string
 					if urlMap, ok := data["image_url"].(map[string]any); ok {
@@ -395,12 +394,12 @@ func fromChatRequest(r ChatCompletionRequest) (*api.ChatRequest, error) {
 					if err != nil {
 						return nil, fmt.Errorf("invalid message format")
 					}
-					message.Images = append(message.Images, img)
+
+					messages = append(messages, api.Message{Role: msg.Role, Images: []api.ImageData{img}})
 				default:
 					return nil, fmt.Errorf("invalid message format")
 				}
 			}
-			messages = append(messages, message)
 		default:
 			if msg.ToolCalls == nil {
 				return nil, fmt.Errorf("invalid message content type: %T", content)
diff --git a/openai/openai_test.go b/openai/openai_test.go
index 046ee69cb..ad056e6d0 100644
--- a/openai/openai_test.go
+++ b/openai/openai_test.go
@@ -161,8 +161,12 @@ func TestMiddlewareRequests(t *testing.T) {
 
 				img, _ := base64.StdEncoding.DecodeString(imageURL[len(prefix):])
 
-				if !bytes.Equal(chatReq.Messages[0].Images[0], img) {
-					t.Fatalf("expected image encoding, got %s", chatReq.Messages[0].Images[0])
+				if chatReq.Messages[1].Role != "user" {
+					t.Fatalf("expected 'user', got %s", chatReq.Messages[1].Role)
+				}
+
+				if !bytes.Equal(chatReq.Messages[1].Images[0], img) {
+					t.Fatalf("expected image encoding, got %s", chatReq.Messages[1].Images[0])
 				}
 			},
 		},

From c57317cbf0c865dd1fbe4852e1cce3cf4703b7ee Mon Sep 17 00:00:00 2001
From: royjhan <65097070+royjhan@users.noreply.github.com>
Date: Fri, 19 Jul 2024 11:37:12 -0700
Subject: [PATCH 112/384] OpenAI: Function Based Testing (#5752)

* distinguish error forwarding

* more coverage

* rm comment
---
 openai/openai.go      |   1 +
 openai/openai_test.go | 461 +++++++++++++++++++++++++-----------------
 2 files changed, 279 insertions(+), 183 deletions(-)

diff --git a/openai/openai.go b/openai/openai.go
index 93b632968..de6f4bd59 100644
--- a/openai/openai.go
+++ b/openai/openai.go
@@ -877,6 +877,7 @@ func ChatMiddleware() gin.HandlerFunc {
 		chatReq, err := fromChatRequest(req)
 		if err != nil {
 			c.AbortWithStatusJSON(http.StatusBadRequest, NewError(http.StatusBadRequest, err.Error()))
+			return
 		}
 
 		if err := json.NewEncoder(&b).Encode(chatReq); err != nil {
diff --git a/openai/openai_test.go b/openai/openai_test.go
index ad056e6d0..f978d46c9 100644
--- a/openai/openai_test.go
+++ b/openai/openai_test.go
@@ -20,113 +20,59 @@ const prefix = `data:image/jpeg;base64,`
 const image = `iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk+A8AAQUBAScY42YAAAAASUVORK5CYII=`
 const imageURL = prefix + image
 
-func TestMiddlewareRequests(t *testing.T) {
+func prepareRequest(req *http.Request, body any) {
+	bodyBytes, _ := json.Marshal(body)
+	req.Body = io.NopCloser(bytes.NewReader(bodyBytes))
+	req.Header.Set("Content-Type", "application/json")
+}
+
+func captureRequestMiddleware(capturedRequest any) gin.HandlerFunc {
+	return func(c *gin.Context) {
+		bodyBytes, _ := io.ReadAll(c.Request.Body)
+		c.Request.Body = io.NopCloser(bytes.NewReader(bodyBytes))
+		err := json.Unmarshal(bodyBytes, capturedRequest)
+		if err != nil {
+			c.AbortWithStatusJSON(http.StatusInternalServerError, "failed to unmarshal request")
+		}
+		c.Next()
+	}
+}
+
+func TestChatMiddleware(t *testing.T) {
 	type testCase struct {
 		Name     string
-		Method   string
-		Path     string
-		Handler  func() gin.HandlerFunc
 		Setup    func(t *testing.T, req *http.Request)
-		Expected func(t *testing.T, req *http.Request)
+		Expected func(t *testing.T, req *api.ChatRequest, resp *httptest.ResponseRecorder)
 	}
 
-	var capturedRequest *http.Request
-
-	captureRequestMiddleware := func() gin.HandlerFunc {
-		return func(c *gin.Context) {
-			bodyBytes, _ := io.ReadAll(c.Request.Body)
-			c.Request.Body = io.NopCloser(bytes.NewReader(bodyBytes))
-			capturedRequest = c.Request
-			c.Next()
-		}
-	}
+	var capturedRequest *api.ChatRequest
 
 	testCases := []testCase{
 		{
-			Name:    "chat handler",
-			Method:  http.MethodPost,
-			Path:    "/api/chat",
-			Handler: ChatMiddleware,
+			Name: "chat handler",
 			Setup: func(t *testing.T, req *http.Request) {
 				body := ChatCompletionRequest{
 					Model:    "test-model",
 					Messages: []Message{{Role: "user", Content: "Hello"}},
 				}
-
-				bodyBytes, _ := json.Marshal(body)
-
-				req.Body = io.NopCloser(bytes.NewReader(bodyBytes))
-				req.Header.Set("Content-Type", "application/json")
+				prepareRequest(req, body)
 			},
-			Expected: func(t *testing.T, req *http.Request) {
-				var chatReq api.ChatRequest
-				if err := json.NewDecoder(req.Body).Decode(&chatReq); err != nil {
-					t.Fatal(err)
+			Expected: func(t *testing.T, req *api.ChatRequest, resp *httptest.ResponseRecorder) {
+				if resp.Code != http.StatusOK {
+					t.Fatalf("expected 200, got %d", resp.Code)
 				}
 
-				if chatReq.Messages[0].Role != "user" {
-					t.Fatalf("expected 'user', got %s", chatReq.Messages[0].Role)
+				if req.Messages[0].Role != "user" {
+					t.Fatalf("expected 'user', got %s", req.Messages[0].Role)
 				}
 
-				if chatReq.Messages[0].Content != "Hello" {
-					t.Fatalf("expected 'Hello', got %s", chatReq.Messages[0].Content)
+				if req.Messages[0].Content != "Hello" {
+					t.Fatalf("expected 'Hello', got %s", req.Messages[0].Content)
 				}
 			},
 		},
 		{
-			Name:    "completions handler",
-			Method:  http.MethodPost,
-			Path:    "/api/generate",
-			Handler: CompletionsMiddleware,
-			Setup: func(t *testing.T, req *http.Request) {
-				temp := float32(0.8)
-				body := CompletionRequest{
-					Model:       "test-model",
-					Prompt:      "Hello",
-					Temperature: &temp,
-					Stop:        []string{"\n", "stop"},
-					Suffix:      "suffix",
-				}
-
-				bodyBytes, _ := json.Marshal(body)
-
-				req.Body = io.NopCloser(bytes.NewReader(bodyBytes))
-				req.Header.Set("Content-Type", "application/json")
-			},
-			Expected: func(t *testing.T, req *http.Request) {
-				var genReq api.GenerateRequest
-				if err := json.NewDecoder(req.Body).Decode(&genReq); err != nil {
-					t.Fatal(err)
-				}
-
-				if genReq.Prompt != "Hello" {
-					t.Fatalf("expected 'Hello', got %s", genReq.Prompt)
-				}
-
-				if genReq.Options["temperature"] != 1.6 {
-					t.Fatalf("expected 1.6, got %f", genReq.Options["temperature"])
-				}
-
-				stopTokens, ok := genReq.Options["stop"].([]any)
-
-				if !ok {
-					t.Fatalf("expected stop tokens to be a list")
-				}
-
-				if stopTokens[0] != "\n" || stopTokens[1] != "stop" {
-					t.Fatalf("expected ['\\n', 'stop'], got %v", stopTokens)
-				}
-
-				if genReq.Suffix != "suffix" {
-					t.Fatalf("expected 'suffix', got %s", genReq.Suffix)
-				}
-			},
-		},
-		{
-			Name:    "chat handler with image content",
-			Method:  http.MethodPost,
-			Path:    "/api/chat",
-			Handler: ChatMiddleware,
+			Name: "chat handler with image content",
 			Setup: func(t *testing.T, req *http.Request) {
 				body := ChatCompletionRequest{
 					Model: "test-model",
@@ -139,91 +85,254 @@ func TestMiddlewareRequests(t *testing.T) {
 						},
 					},
 				}
-
-				bodyBytes, _ := json.Marshal(body)
-
-				req.Body = io.NopCloser(bytes.NewReader(bodyBytes))
-				req.Header.Set("Content-Type", "application/json")
+				prepareRequest(req, body)
 			},
-			Expected: func(t *testing.T, req *http.Request) {
-				var chatReq api.ChatRequest
-				if err := json.NewDecoder(req.Body).Decode(&chatReq); err != nil {
-					t.Fatal(err)
+			Expected: func(t *testing.T, req *api.ChatRequest, resp *httptest.ResponseRecorder) {
+				if resp.Code != http.StatusOK {
+					t.Fatalf("expected 200, got %d", resp.Code)
 				}
 
-				if chatReq.Messages[0].Role != "user" {
-					t.Fatalf("expected 'user', got %s", chatReq.Messages[0].Role)
+				if req.Messages[0].Role != "user" {
+					t.Fatalf("expected 'user', got %s", req.Messages[0].Role)
 				}
 
-				if chatReq.Messages[0].Content != "Hello" {
-					t.Fatalf("expected 'Hello', got %s", chatReq.Messages[0].Content)
+				if req.Messages[0].Content != "Hello" {
+					t.Fatalf("expected 'Hello', got %s", req.Messages[0].Content)
 				}
 
 				img, _ := base64.StdEncoding.DecodeString(imageURL[len(prefix):])
 
-				if chatReq.Messages[1].Role != "user" {
-					t.Fatalf("expected 'user', got %s", chatReq.Messages[1].Role)
+				if req.Messages[1].Role != "user" {
+					t.Fatalf("expected 'user', got %s", req.Messages[1].Role)
 				}
 
-				if !bytes.Equal(chatReq.Messages[1].Images[0], img) {
-					t.Fatalf("expected image encoding, got %s", chatReq.Messages[1].Images[0])
+				if !bytes.Equal(req.Messages[1].Images[0], img) {
+					t.Fatalf("expected image encoding, got %s", req.Messages[1].Images[0])
 				}
 			},
 		},
 		{
-			Name:    "embed handler single input",
-			Method:  http.MethodPost,
-			Path:    "/api/embed",
-			Handler: EmbeddingsMiddleware,
+			Name: "chat handler with tools",
+			Setup: func(t *testing.T, req *http.Request) {
+				body := ChatCompletionRequest{
+					Model: "test-model",
+					Messages: []Message{
+						{Role: "user", Content: "What's the weather like in Paris Today?"},
+						{Role: "assistant", ToolCalls: []ToolCall{{
+							ID:   "id",
+							Type: "function",
+							Function: struct {
+								Name      string `json:"name"`
+								Arguments string `json:"arguments"`
+							}{
+								Name:      "get_current_weather",
+								Arguments: "{\"location\": \"Paris, France\", \"format\": \"celsius\"}",
+							},
+						}}},
+					},
+				}
+				prepareRequest(req, body)
+			},
+			Expected: func(t *testing.T, req *api.ChatRequest, resp *httptest.ResponseRecorder) {
+				if resp.Code != 200 {
+					t.Fatalf("expected 200, got %d", resp.Code)
+				}
+
+				if req.Messages[0].Content != "What's the weather like in Paris Today?" {
+					t.Fatalf("expected What's the weather like in Paris Today?, got %s", req.Messages[0].Content)
+				}
+
+				if req.Messages[1].ToolCalls[0].Function.Arguments["location"] != "Paris, France" {
+					t.Fatalf("expected 'Paris, France', got %v", req.Messages[1].ToolCalls[0].Function.Arguments["location"])
+				}
+
+				if req.Messages[1].ToolCalls[0].Function.Arguments["format"] != "celsius" {
+					t.Fatalf("expected celsius, got %v", req.Messages[1].ToolCalls[0].Function.Arguments["format"])
+				}
+			},
+		},
+		{
+			Name: "chat handler error forwarding",
+			Setup: func(t *testing.T, req *http.Request) {
+				body := ChatCompletionRequest{
+					Model:    "test-model",
+					Messages: []Message{{Role: "user", Content: 2}},
+				}
+				prepareRequest(req, body)
+			},
+			Expected: func(t *testing.T, req *api.ChatRequest, resp *httptest.ResponseRecorder) {
+				if resp.Code != http.StatusBadRequest {
+					t.Fatalf("expected 400, got %d", resp.Code)
+				}
+
+				if !strings.Contains(resp.Body.String(), "invalid message content type") {
+					t.Fatalf("error was not forwarded")
+				}
+			},
+		},
+	}
+
+	endpoint := func(c *gin.Context) {
+		c.Status(http.StatusOK)
+	}
+
+	gin.SetMode(gin.TestMode)
+	router := gin.New()
+	router.Use(ChatMiddleware(), captureRequestMiddleware(&capturedRequest))
+	router.Handle(http.MethodPost, "/api/chat", endpoint)
+
+	for _, tc := range testCases {
+		t.Run(tc.Name, func(t *testing.T) {
+			req, _ := http.NewRequest(http.MethodPost, "/api/chat", nil)
+
+			tc.Setup(t, req)
+
+			resp := httptest.NewRecorder()
+			router.ServeHTTP(resp, req)
+
+			tc.Expected(t, capturedRequest, resp)
+
+			capturedRequest = nil
+		})
+	}
+}
+
+func TestCompletionsMiddleware(t *testing.T) {
+	type testCase struct {
+		Name     string
+		Setup    func(t *testing.T, req *http.Request)
+		Expected func(t *testing.T, req *api.GenerateRequest, resp *httptest.ResponseRecorder)
+	}
+
+	var capturedRequest *api.GenerateRequest
+
+	testCases := []testCase{
+		{
+			Name: "completions handler",
+			Setup: func(t *testing.T, req *http.Request) {
+				temp := float32(0.8)
+				body := CompletionRequest{
+					Model:       "test-model",
+					Prompt:      "Hello",
+					Temperature: &temp,
+					Stop:        []string{"\n", "stop"},
+					Suffix:      "suffix",
+				}
+				prepareRequest(req, body)
+			},
+			Expected: func(t *testing.T, req *api.GenerateRequest, resp *httptest.ResponseRecorder) {
+				if req.Prompt != "Hello" {
+					t.Fatalf("expected 'Hello', got %s", req.Prompt)
+				}
+
+				if req.Options["temperature"] != 1.6 {
+					t.Fatalf("expected 1.6, got %f", req.Options["temperature"])
+				}
+
+				stopTokens, ok := req.Options["stop"].([]any)
+
+				if !ok {
+					t.Fatalf("expected stop tokens to be a list")
+				}
+
+				if stopTokens[0] != "\n" || stopTokens[1] != "stop" {
+					t.Fatalf("expected ['\\n', 'stop'], got %v", stopTokens)
+				}
+
+				if req.Suffix != "suffix" {
+					t.Fatalf("expected 'suffix', got %s", req.Suffix)
+				}
+			},
+		},
+		{
+			Name: "completions handler error forwarding",
+			Setup: func(t *testing.T, req *http.Request) {
+				body := CompletionRequest{
+					Model:       "test-model",
+					Prompt:      "Hello",
+					Temperature: nil,
+					Stop:        []int{1, 2},
+					Suffix:      "suffix",
+				}
+				prepareRequest(req, body)
+			},
+			Expected: func(t *testing.T, req *api.GenerateRequest, resp *httptest.ResponseRecorder) {
+				if resp.Code != http.StatusBadRequest {
+					t.Fatalf("expected 400, got %d", resp.Code)
+				}
+
+				if !strings.Contains(resp.Body.String(), "invalid type for 'stop' field") {
+					t.Fatalf("error was not forwarded")
+				}
+			},
+		},
+	}
+
+	endpoint := func(c *gin.Context) {
+		c.Status(http.StatusOK)
+	}
+
+	gin.SetMode(gin.TestMode)
+	router := gin.New()
+	router.Use(CompletionsMiddleware(), captureRequestMiddleware(&capturedRequest))
+	router.Handle(http.MethodPost, "/api/generate", endpoint)
+
+	for _, tc := range testCases {
+		t.Run(tc.Name, func(t *testing.T) {
+			req, _ := http.NewRequest(http.MethodPost, "/api/generate", nil)
+
+			tc.Setup(t, req)
+
+			resp := httptest.NewRecorder()
+			router.ServeHTTP(resp, req)
+
+			tc.Expected(t, capturedRequest, resp)
+
+			capturedRequest = nil
+		})
+	}
+}
+
+func TestEmbeddingsMiddleware(t *testing.T) {
+	type testCase struct {
+		Name     string
+		Setup    func(t *testing.T, req *http.Request)
+		Expected func(t *testing.T, req *api.EmbedRequest, resp *httptest.ResponseRecorder)
+	}
+
+	var capturedRequest *api.EmbedRequest
+
+	testCases := []testCase{
+		{
+			Name: "embed handler single input",
 			Setup: func(t *testing.T, req *http.Request) {
 				body := EmbedRequest{
 					Input: "Hello",
 					Model: "test-model",
 				}
-
-				bodyBytes, _ := json.Marshal(body)
-
-				req.Body = io.NopCloser(bytes.NewReader(bodyBytes))
-				req.Header.Set("Content-Type", "application/json")
+				prepareRequest(req, body)
 			},
-			Expected: func(t *testing.T, req *http.Request) {
-				var embedReq api.EmbedRequest
-				if err := json.NewDecoder(req.Body).Decode(&embedReq); err != nil {
-					t.Fatal(err)
+			Expected: func(t *testing.T, req *api.EmbedRequest, resp *httptest.ResponseRecorder) {
+				if req.Input != "Hello" {
+					t.Fatalf("expected 'Hello', got %s", req.Input)
 				}
 
-				if embedReq.Input != "Hello" {
-					t.Fatalf("expected 'Hello', got %s", embedReq.Input)
-				}
-
-				if embedReq.Model != "test-model" {
-					t.Fatalf("expected 'test-model', got %s", embedReq.Model)
+				if req.Model != "test-model" {
+					t.Fatalf("expected 'test-model', got %s", req.Model)
 				}
 			},
 		},
 		{
-			Name:    "embed handler batch input",
-			Method:  http.MethodPost,
-			Path:    "/api/embed",
-			Handler: EmbeddingsMiddleware,
+			Name: "embed handler batch input",
 			Setup: func(t *testing.T, req *http.Request) {
 				body := EmbedRequest{
 					Input: []string{"Hello", "World"},
 					Model: "test-model",
 				}
-
-				bodyBytes, _ := json.Marshal(body)
-
-				req.Body = io.NopCloser(bytes.NewReader(bodyBytes))
-				req.Header.Set("Content-Type", "application/json")
+				prepareRequest(req, body)
 			},
-			Expected: func(t *testing.T, req *http.Request) {
-				var embedReq api.EmbedRequest
-				if err := json.NewDecoder(req.Body).Decode(&embedReq); err != nil {
-					t.Fatal(err)
-				}
-
-				input, ok := embedReq.Input.([]any)
+			Expected: func(t *testing.T, req *api.EmbedRequest, resp *httptest.ResponseRecorder) {
+				input, ok := req.Input.([]any)
 
 				if !ok {
 					t.Fatalf("expected input to be a list")
@@ -237,36 +346,52 @@ func TestMiddlewareRequests(t *testing.T) {
 					t.Fatalf("expected 'World', got %s", input[1])
 				}
 
-				if embedReq.Model != "test-model" {
-					t.Fatalf("expected 'test-model', got %s", embedReq.Model)
+				if req.Model != "test-model" {
+					t.Fatalf("expected 'test-model', got %s", req.Model)
+				}
+			},
+		},
+		{
+			Name: "embed handler error forwarding",
+			Setup: func(t *testing.T, req *http.Request) {
+				body := EmbedRequest{
+					Model: "test-model",
+				}
+				prepareRequest(req, body)
+			},
+			Expected: func(t *testing.T, req *api.EmbedRequest, resp *httptest.ResponseRecorder) {
+				if resp.Code != http.StatusBadRequest {
+					t.Fatalf("expected 400, got %d", resp.Code)
+				}
+
+				if !strings.Contains(resp.Body.String(), "invalid input") {
+					t.Fatalf("error was not forwarded")
 				}
 			},
 		},
 	}
 
-	gin.SetMode(gin.TestMode)
-	router := gin.New()
-
 	endpoint := func(c *gin.Context) {
 		c.Status(http.StatusOK)
 	}
 
+	gin.SetMode(gin.TestMode)
+	router := gin.New()
+	router.Use(EmbeddingsMiddleware(), captureRequestMiddleware(&capturedRequest))
+	router.Handle(http.MethodPost, "/api/embed", endpoint)
+
 	for _, tc := range testCases {
 		t.Run(tc.Name, func(t *testing.T) {
-			router = gin.New()
-			router.Use(captureRequestMiddleware())
-			router.Use(tc.Handler())
-			router.Handle(tc.Method, tc.Path, endpoint)
-			req, _ := http.NewRequest(tc.Method, tc.Path, nil)
+			req, _ := http.NewRequest(http.MethodPost, "/api/embed", nil)
 
-			if tc.Setup != nil {
-				tc.Setup(t, req)
-			}
+			tc.Setup(t, req)
 
 			resp := httptest.NewRecorder()
 			router.ServeHTTP(resp, req)
 
-			tc.Expected(t, capturedRequest)
+			tc.Expected(t, capturedRequest, resp)
+
+			capturedRequest = nil
 		})
 	}
 }
@@ -284,36 +409,6 @@ func TestMiddlewareResponses(t *testing.T) {
 	}
 
 	testCases := []testCase{
-		{
-			Name:     "completions handler error forwarding",
-			Method:   http.MethodPost,
-			Path:     "/api/generate",
-			TestPath: "/api/generate",
-			Handler:  CompletionsMiddleware,
-			Endpoint: func(c *gin.Context) {
-				c.JSON(http.StatusBadRequest, gin.H{"error": "invalid request"})
-			},
-			Setup: func(t *testing.T, req *http.Request) {
-				body := CompletionRequest{
-					Model:  "test-model",
-					Prompt: "Hello",
-				}
-
-				bodyBytes, _ := json.Marshal(body)
-
-				req.Body = io.NopCloser(bytes.NewReader(bodyBytes))
-				req.Header.Set("Content-Type", "application/json")
-			},
-			Expected: func(t *testing.T, resp *httptest.ResponseRecorder) {
-				if resp.Code != http.StatusBadRequest {
-					t.Fatalf("expected 400, got %d", resp.Code)
-				}
-
-				if !strings.Contains(resp.Body.String(), `"invalid request"`) {
-					t.Fatalf("error was not forwarded")
-				}
-			},
-		},
 		{
 			Name:     "list handler",
 			Method:   http.MethodGet,
@@ -330,8 +425,6 @@ func TestMiddlewareResponses(t *testing.T) {
 				})
 			},
 			Expected: func(t *testing.T, resp *httptest.ResponseRecorder) {
-				assert.Equal(t, http.StatusOK, resp.Code)
-
 				var listResp ListCompletion
 				if err := json.NewDecoder(resp.Body).Decode(&listResp); err != nil {
 					t.Fatal(err)
@@ -395,6 +488,8 @@ func TestMiddlewareResponses(t *testing.T) {
 			resp := httptest.NewRecorder()
 			router.ServeHTTP(resp, req)
 
+			assert.Equal(t, http.StatusOK, resp.Code)
+
 			tc.Expected(t, resp)
 		})
 	}

From e8b954c646544d40d84be50aae9cd909fcbd8f41 Mon Sep 17 00:00:00 2001
From: Josh <76125168+joshyan1@users.noreply.github.com>
Date: Fri, 19 Jul 2024 15:24:29 -0700
Subject: [PATCH 113/384] server: validate template (#5734)

add template validation to modelfile
---
 server/images.go             |  6 ++++++
 server/routes.go             | 14 +++++++++++---
 server/routes_create_test.go | 36 ++++++++++++++++++++++++++++++++++++
 3 files changed, 53 insertions(+), 3 deletions(-)

diff --git a/server/images.go b/server/images.go
index 5e4e88583..574dec191 100644
--- a/server/images.go
+++ b/server/images.go
@@ -492,6 +492,12 @@ func CreateModel(ctx context.Context, name model.Name, modelFileDir, quantizatio
 				layers = append(layers, baseLayer.Layer)
 			}
 		case "license", "template", "system":
+			if c.Name == "template" {
+				if _, err := template.Parse(c.Args); err != nil {
+					return fmt.Errorf("%w: %s", errBadTemplate, err)
+				}
+			}
+
 			if c.Name != "license" {
 				// replace
 				layers = slices.DeleteFunc(layers, func(layer *Layer) bool {
diff --git a/server/routes.go b/server/routes.go
index c33b71954..85db79249 100644
--- a/server/routes.go
+++ b/server/routes.go
@@ -56,6 +56,7 @@ func init() {
 }
 
 var errRequired = errors.New("is required")
+var errBadTemplate = errors.New("template error")
 
 func modelOptions(model *Model, requestOpts map[string]interface{}) (api.Options, error) {
 	opts := api.DefaultOptions()
@@ -609,8 +610,11 @@ func (s *Server) CreateModelHandler(c *gin.Context) {
 
 		quantization := cmp.Or(r.Quantize, r.Quantization)
 		if err := CreateModel(ctx, name, filepath.Dir(r.Path), strings.ToUpper(quantization), f, fn); err != nil {
+			if errors.Is(err, errBadTemplate) {
+			  ch <- gin.H{"error": err.Error(), "status": http.StatusBadRequest}
+			}
 			ch <- gin.H{"error": err.Error()}
-		}
+		  }
 	}()
 
 	if r.Stream != nil && !*r.Stream {
@@ -1196,11 +1200,15 @@ func waitForStream(c *gin.Context, ch chan interface{}) {
 				return
 			}
 		case gin.H:
+			status, ok := r["status"].(int)
+			if !ok {
+				status = http.StatusInternalServerError
+			}
 			if errorMsg, ok := r["error"].(string); ok {
-				c.JSON(http.StatusInternalServerError, gin.H{"error": errorMsg})
+				c.JSON(status, gin.H{"error": errorMsg})
 				return
 			} else {
-				c.JSON(http.StatusInternalServerError, gin.H{"error": "unexpected error format in progress response"})
+				c.JSON(status, gin.H{"error": "unexpected error format in progress response"})
 				return
 			}
 		default:
diff --git a/server/routes_create_test.go b/server/routes_create_test.go
index cb548ebda..3234ea5e1 100644
--- a/server/routes_create_test.go
+++ b/server/routes_create_test.go
@@ -491,6 +491,42 @@ func TestCreateTemplateSystem(t *testing.T) {
 	if string(system) != "Say bye!" {
 		t.Errorf("expected \"Say bye!\", actual %s", system)
 	}
+
+	t.Run("incomplete template", func(t *testing.T) {
+		w := createRequest(t, s.CreateModelHandler, api.CreateRequest{
+			Name:      "test",
+			Modelfile: fmt.Sprintf("FROM %s\nTEMPLATE {{ .Prompt", createBinFile(t, nil, nil)),
+			Stream:    &stream,
+		})
+	
+		if w.Code != http.StatusBadRequest {
+			t.Fatalf("expected status code 400, actual %d", w.Code)
+		}
+	})
+
+	t.Run("template with unclosed if", func(t *testing.T) {
+		w := createRequest(t, s.CreateModelHandler, api.CreateRequest{
+			Name:      "test",
+			Modelfile: fmt.Sprintf("FROM %s\nTEMPLATE {{ if .Prompt }}", createBinFile(t, nil, nil)),
+			Stream:    &stream,
+		})
+	
+		if w.Code != http.StatusBadRequest {
+			t.Fatalf("expected status code 400, actual %d", w.Code)
+		}
+	})
+
+	t.Run("template with undefined function", func(t *testing.T) {
+		w := createRequest(t, s.CreateModelHandler, api.CreateRequest{
+			Name:      "test",
+			Modelfile: fmt.Sprintf("FROM %s\nTEMPLATE {{  Prompt }}", createBinFile(t, nil, nil)),
+			Stream:    &stream,
+		})
+	
+		if w.Code != http.StatusBadRequest {
+			t.Fatalf("expected status code 400, actual %d", w.Code)
+		}
+	})
 }
 
 func TestCreateLicenses(t *testing.T) {

From 69a2d4ccffa7532680bed245fad77bb166ec0bb9 Mon Sep 17 00:00:00 2001
From: Jeffrey Morgan 
Date: Fri, 19 Jul 2024 19:11:25 -0700
Subject: [PATCH 114/384] Fix generate test flakyness (#5804)

---
 server/routes_generate_test.go | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)

diff --git a/server/routes_generate_test.go b/server/routes_generate_test.go
index c914b3006..5c0caff1c 100644
--- a/server/routes_generate_test.go
+++ b/server/routes_generate_test.go
@@ -73,8 +73,8 @@ func TestGenerateChat(t *testing.T) {
 			getCpuFn:      gpu.GetCPUInfo,
 			reschedDelay:  250 * time.Millisecond,
 			loadFn: func(req *LlmRequest, ggml *llm.GGML, gpus gpu.GpuInfoList, numParallel int) {
-				// add 10ms delay to simulate loading
-				time.Sleep(10 * time.Millisecond)
+				// add small delay to simulate loading
+				time.Sleep(time.Millisecond)
 				req.successCh <- &runnerRef{
 					llama: &mock,
 				}
@@ -371,6 +371,8 @@ func TestGenerate(t *testing.T) {
 			getCpuFn:      gpu.GetCPUInfo,
 			reschedDelay:  250 * time.Millisecond,
 			loadFn: func(req *LlmRequest, ggml *llm.GGML, gpus gpu.GpuInfoList, numParallel int) {
+				// add small delay to simulate loading
+				time.Sleep(time.Millisecond)
 				req.successCh <- &runnerRef{
 					llama: &mock,
 				}

From 20090f3172c4848584060cbd51e7c9b14c3630cb Mon Sep 17 00:00:00 2001
From: Jeffrey Morgan 
Date: Fri, 19 Jul 2024 20:19:26 -0700
Subject: [PATCH 115/384] preserve last assistant message (#5802)

---
 template/template.go      |  3 ++-
 template/template_test.go | 20 ++++++++++++++++++++
 2 files changed, 22 insertions(+), 1 deletion(-)

diff --git a/template/template.go b/template/template.go
index b5bfb16ce..f74537910 100644
--- a/template/template.go
+++ b/template/template.go
@@ -264,6 +264,7 @@ func (t *Template) Execute(w io.Writer, v Values) error {
 	nodes := deleteNode(t.Template.Root.Copy(), func(n parse.Node) bool {
 		if field, ok := n.(*parse.FieldNode); ok && slices.Contains(field.Ident, "Response") {
 			cut = true
+			return false
 		}
 
 		return cut
@@ -273,7 +274,7 @@ func (t *Template) Execute(w io.Writer, v Values) error {
 	if err := template.Must(template.New("").AddParseTree("", &tree)).Execute(&b, map[string]any{
 		"System":   system,
 		"Prompt":   prompt,
-		"Response": "",
+		"Response": response,
 	}); err != nil {
 		return err
 	}
diff --git a/template/template_test.go b/template/template_test.go
index ae0db80b9..b46e1df5b 100644
--- a/template/template_test.go
+++ b/template/template_test.go
@@ -260,6 +260,26 @@ func TestExecuteWithMessages(t *testing.T) {
 
 Hello friend![/INST] Hello human![INST] What is your name?[/INST] `,
 		},
+		{
+			"mistral assistant",
+			[]template{
+				{"no response", `[INST] {{ .Prompt }}[/INST] `},
+				{"response", `[INST] {{ .Prompt }}[/INST] {{ .Response }}`},
+				{"messages", `
+{{- range $i, $m := .Messages }}
+{{- if eq .Role "user" }}[INST] {{ .Content }}[/INST] {{ else if eq .Role "assistant" }}{{ .Content }}{{ end }}
+{{- end }}`},
+			},
+			Values{
+				Messages: []api.Message{
+					{Role: "user", Content: "Hello friend!"},
+					{Role: "assistant", Content: "Hello human!"},
+					{Role: "user", Content: "What is your name?"},
+					{Role: "assistant", Content: "My name is Ollama and I"},
+				},
+			},
+			`[INST] Hello friend![/INST] Hello human![INST] What is your name?[/INST] My name is Ollama and I`,
+		},
 		{
 			"chatml",
 			[]template{

From 1475eab95f5a4ddc5b1bb169df0d89e71732dfa0 Mon Sep 17 00:00:00 2001
From: Jeffrey Morgan 
Date: Sat, 20 Jul 2024 13:41:21 -0400
Subject: [PATCH 116/384] add patch for tekken (#5807)

---
 llm/patches/10-tekken.diff | 43 ++++++++++++++++++++++++++++++++++++++
 1 file changed, 43 insertions(+)
 create mode 100644 llm/patches/10-tekken.diff

diff --git a/llm/patches/10-tekken.diff b/llm/patches/10-tekken.diff
new file mode 100644
index 000000000..56a583e0c
--- /dev/null
+++ b/llm/patches/10-tekken.diff
@@ -0,0 +1,43 @@
+diff --git a/include/llama.h b/include/llama.h
+index bb4b05ba..a92174e0 100644
+--- a/include/llama.h
++++ b/include/llama.h
+@@ -92,6 +92,7 @@ extern "C" {
+         LLAMA_VOCAB_PRE_TYPE_CHATGLM4       = 17,
+         LLAMA_VOCAB_PRE_TYPE_VIKING         = 18,
+         LLAMA_VOCAB_PRE_TYPE_JAIS           = 19,
++        LLAMA_VOCAB_PRE_TYPE_TEKKEN         = 20,
+     };
+ 
+     // note: these values should be synchronized with ggml_rope
+diff --git a/src/llama.cpp b/src/llama.cpp
+index 18364976..435b6fe5 100644
+--- a/src/llama.cpp
++++ b/src/llama.cpp
+@@ -5429,6 +5429,12 @@ static void llm_load_vocab(
+             } else if (
+                 tokenizer_pre == "jais") {
+                 vocab.type_pre = LLAMA_VOCAB_PRE_TYPE_JAIS;
++            } else if (
++                tokenizer_pre == "tekken") {
++                vocab.type_pre = LLAMA_VOCAB_PRE_TYPE_TEKKEN;
++                vocab.tokenizer_clean_spaces = false;
++                vocab.tokenizer_ignore_merges = true;
++                vocab.tokenizer_add_bos = true;
+             } else {
+                 LLAMA_LOG_WARN("%s: missing or unrecognized pre-tokenizer type, using: 'default'\n", __func__);
+                 vocab.type_pre = LLAMA_VOCAB_PRE_TYPE_DEFAULT;
+@@ -15448,6 +15454,13 @@ struct llm_tokenizer_bpe {
+                     " ?[^(\\s|.,!?…。,、।۔،)]+",
+                 };
+                 break;
++            case LLAMA_VOCAB_PRE_TYPE_TEKKEN:
++                    // original regex from tokenizer.json
++                    // "[^\\r\\n\\p{L}\\p{N}]?[\\p{Lu}\\p{Lt}\\p{Lm}\\p{Lo}\\p{M}]*[\\p{Ll}\\p{Lm}\\p{Lo}\\p{M}]+|[^\\r\\n\\p{L}\\p{N}]?[\\p{Lu}\\p{Lt}\\p{Lm}\\p{Lo}\\p{M}]+[\\p{Ll}\\p{Lm}\\p{Lo}\\p{M}]*|\\p{N}| ?[^\\s\\p{L}\\p{N}]+[\\r\\n/]*|\\s*[\\r\\n]+|\\s+(?!\\S)|\\s+"
++                regex_exprs = {
++                    "[^\\r\\n\\p{L}\\p{N}]?((?=[\\p{L}])([^a-z]))*((?=[\\p{L}])([^A-Z]))+|[^\\r\\n\\p{L}\\p{N}]?((?=[\\p{L}])([^a-z]))+((?=[\\p{L}])([^A-Z]))*|\\p{N}| ?[^\\s\\p{L}\\p{N}]+[\\r\\n/]*|\\s*[\\r\\n]+|\\s+(?!\\S)|\\s+",
++                };
++                break;
+             default:
+                 // default regex for BPE tokenization pre-processing
+                 regex_exprs = {

From 283948c83b5cbf74f6cf86dce4434238e64d6e1c Mon Sep 17 00:00:00 2001
From: Daniel Hiltgen 
Date: Fri, 19 Jul 2024 15:07:26 -0700
Subject: [PATCH 117/384] Adjust windows ROCm discovery

The v5 hip library returns unsupported GPUs which wont enumerate at
inference time in the runner so this makes sure we align discovery.  The
gfx906 cards are no longer supported so we shouldn't compile with that
GPU type as it wont enumerate at runtime.
---
 docs/gpu.md                  | 15 +++++++++++++--
 gpu/amd_hip_windows.go       |  5 +++--
 gpu/amd_windows.go           |  3 ++-
 llm/generate/gen_windows.ps1 |  2 +-
 llm/server.go                |  2 ++
 5 files changed, 21 insertions(+), 6 deletions(-)

diff --git a/docs/gpu.md b/docs/gpu.md
index 80f276c3b..e669ea32c 100644
--- a/docs/gpu.md
+++ b/docs/gpu.md
@@ -46,13 +46,24 @@ sudo modprobe nvidia_uvm`
 
 ## AMD Radeon
 Ollama supports the following AMD GPUs:
+
+### Linux Support
 | Family         | Cards and accelerators                                                                                                               |
 | -------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- |
 | AMD Radeon RX  | `7900 XTX` `7900 XT` `7900 GRE` `7800 XT` `7700 XT` `7600 XT` `7600` `6950 XT` `6900 XTX` `6900XT` `6800 XT` `6800` `Vega 64` `Vega 56`    |
 | AMD Radeon PRO | `W7900` `W7800` `W7700` `W7600` `W7500` `W6900X` `W6800X Duo` `W6800X` `W6800` `V620` `V420` `V340` `V320` `Vega II Duo` `Vega II` `VII` `SSG` |
 | AMD Instinct   | `MI300X` `MI300A` `MI300` `MI250X` `MI250` `MI210` `MI200` `MI100` `MI60` `MI50`                                                               |
 
-### Overrides
+### Windows Support
+With ROCm v6.1, the following GPUs are supported on Windows.
+
+| Family         | Cards and accelerators                                                                                                               |
+| -------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- |
+| AMD Radeon RX  | `7900 XTX` `7900 XT` `7900 GRE` `7800 XT` `7700 XT` `7600 XT` `7600` `6950 XT` `6900 XTX` `6900XT` `6800 XT` `6800`    |
+| AMD Radeon PRO | `W7900` `W7800` `W7700` `W7600` `W7500` `W6900X` `W6800X Duo` `W6800X` `W6800` `V620` |
+
+
+### Overrides on Linux
 Ollama leverages the AMD ROCm library, which does not support all AMD GPUs. In
 some cases you can force the system to try to use a similar LLVM target that is
 close.  For example The Radeon RX 5400 is `gfx1034` (also known as 10.3.4)
@@ -63,7 +74,7 @@ would set `HSA_OVERRIDE_GFX_VERSION="10.3.0"` as an environment variable for the
 server.  If you have an unsupported AMD GPU you can experiment using the list of
 supported types below.
 
-At this time, the known supported GPU types are the following LLVM Targets.
+At this time, the known supported GPU types on linux are the following LLVM Targets.
 This table shows some example GPUs that map to these LLVM targets:
 | **LLVM Target** | **An Example GPU** |
 |-----------------|---------------------|
diff --git a/gpu/amd_hip_windows.go b/gpu/amd_hip_windows.go
index 2586278c8..98806234c 100644
--- a/gpu/amd_hip_windows.go
+++ b/gpu/amd_hip_windows.go
@@ -33,9 +33,10 @@ type HipLib struct {
 }
 
 func NewHipLib() (*HipLib, error) {
-	h, err := windows.LoadLibrary("amdhip64.dll")
+	// At runtime we depend on v6, so discover GPUs with the same library for a consistent set of GPUs
+	h, err := windows.LoadLibrary("amdhip64_6.dll")
 	if err != nil {
-		return nil, fmt.Errorf("unable to load amdhip64.dll: %w", err)
+		return nil, fmt.Errorf("unable to load amdhip64_6.dll, please make sure to upgrade to the latest amd driver: %w", err)
 	}
 	hl := &HipLib{}
 	hl.dll = h
diff --git a/gpu/amd_windows.go b/gpu/amd_windows.go
index 425259d71..20aed4478 100644
--- a/gpu/amd_windows.go
+++ b/gpu/amd_windows.go
@@ -92,7 +92,8 @@ func AMDGetGPUInfo() []RocmGPUInfo {
 			continue
 		}
 		if gfxOverride == "" {
-			if !slices.Contains[[]string, string](supported, gfx) {
+			// Strip off Target Features when comparing
+			if !slices.Contains[[]string, string](supported, strings.Split(gfx, ":")[0]) {
 				slog.Warn("amdgpu is not supported", "gpu", i, "gpu_type", gfx, "library", libDir, "supported_types", supported)
 				// TODO - consider discrete markdown just for ROCM troubleshooting?
 				slog.Warn("See https://github.com/ollama/ollama/blob/main/docs/troubleshooting.md for HSA_OVERRIDE_GFX_VERSION usage")
diff --git a/llm/generate/gen_windows.ps1 b/llm/generate/gen_windows.ps1
index beb964f98..d8bce92d6 100644
--- a/llm/generate/gen_windows.ps1
+++ b/llm/generate/gen_windows.ps1
@@ -7,8 +7,8 @@ function amdGPUs {
         return $env:AMDGPU_TARGETS
     }
     # Current supported rocblas list from ROCm v6.1.2 on windows
+    # https://rocm.docs.amd.com/projects/install-on-windows/en/latest/reference/system-requirements.html#windows-supported-gpus
     $GPU_LIST = @(
-        "gfx906:xnack-"
         "gfx1030"
         "gfx1100"
         "gfx1101"
diff --git a/llm/server.go b/llm/server.go
index 36c0e0b55..ba7eab03c 100644
--- a/llm/server.go
+++ b/llm/server.go
@@ -385,8 +385,10 @@ func NewLlamaServer(gpus gpu.GpuInfoList, model string, ggml *GGML, adapters, pr
 			filteredEnv := []string{}
 			for _, ev := range s.cmd.Env {
 				if strings.HasPrefix(ev, "CUDA_") ||
+					strings.HasPrefix(ev, "ROCR_") ||
 					strings.HasPrefix(ev, "ROCM_") ||
 					strings.HasPrefix(ev, "HIP_") ||
+					strings.HasPrefix(ev, "GPU_") ||
 					strings.HasPrefix(ev, "HSA_") ||
 					strings.HasPrefix(ev, "GGML_") ||
 					strings.HasPrefix(ev, "PATH=") ||

From 5534f2cc6a3f29022998950472741d16e7a66b40 Mon Sep 17 00:00:00 2001
From: Jeffrey Morgan 
Date: Sat, 20 Jul 2024 21:48:12 -0400
Subject: [PATCH 118/384] llm: consider `head_dim` in llama arch (#5817)

---
 llm/patches/11-embd_kv.diff | 19 +++++++++++++++++++
 1 file changed, 19 insertions(+)
 create mode 100644 llm/patches/11-embd_kv.diff

diff --git a/llm/patches/11-embd_kv.diff b/llm/patches/11-embd_kv.diff
new file mode 100644
index 000000000..ad17a700d
--- /dev/null
+++ b/llm/patches/11-embd_kv.diff
@@ -0,0 +1,19 @@
+diff --git a/src/llama.cpp b/src/llama.cpp
+index 2b9ace28..e60d3d8d 100644
+--- a/src/llama.cpp
++++ b/src/llama.cpp
+@@ -6052,10 +6052,10 @@ static bool llm_load_tensors(
+ 
+                         layer.attn_norm = ml.create_tensor(ctx_layer, tn(LLM_TENSOR_ATTN_NORM, "weight", i), {n_embd});
+ 
+-                        layer.wq = ml.create_tensor(ctx_split, tn(LLM_TENSOR_ATTN_Q,   "weight", i), {n_embd, n_embd});
+-                        layer.wk = ml.create_tensor(ctx_split, tn(LLM_TENSOR_ATTN_K,   "weight", i), {n_embd, n_embd_gqa});
+-                        layer.wv = ml.create_tensor(ctx_split, tn(LLM_TENSOR_ATTN_V,   "weight", i), {n_embd, n_embd_gqa});
+-                        layer.wo = ml.create_tensor(ctx_split, tn(LLM_TENSOR_ATTN_OUT, "weight", i), {n_embd, n_embd});
++                        layer.wq = ml.create_tensor(ctx_split, tn(LLM_TENSOR_ATTN_Q,   "weight", i), {n_embd,  n_embd_head_k * n_head});
++                        layer.wk = ml.create_tensor(ctx_split, tn(LLM_TENSOR_ATTN_K,   "weight", i), {n_embd, n_embd_k_gqa});
++                        layer.wv = ml.create_tensor(ctx_split, tn(LLM_TENSOR_ATTN_V,   "weight", i), {n_embd, n_embd_v_gqa});
++                        layer.wo = ml.create_tensor(ctx_split, tn(LLM_TENSOR_ATTN_OUT, "weight", i), {n_embd_head_k * n_head, n_embd});
+ 
+                         // optional bias tensors
+                         layer.bq = ml.create_tensor(ctx_layer, tn(LLM_TENSOR_ATTN_Q,   "bias", i), {n_embd},     llama_model_loader::TENSOR_NOT_REQUIRED);

From 80ee9b5e47fc0ea99d1f3f33224923266627c15c Mon Sep 17 00:00:00 2001
From: Jeffrey Morgan 
Date: Sun, 21 Jul 2024 00:22:11 -0400
Subject: [PATCH 119/384] Remove out of space test temporarily (#5825)

---
 server/sched_test.go | 37 -------------------------------------
 1 file changed, 37 deletions(-)

diff --git a/server/sched_test.go b/server/sched_test.go
index 7991e7c57..9ddd1fabe 100644
--- a/server/sched_test.go
+++ b/server/sched_test.go
@@ -7,7 +7,6 @@ import (
 	"fmt"
 	"log/slog"
 	"os"
-	"runtime"
 	"testing"
 	"time"
 
@@ -356,42 +355,6 @@ func TestRequestsMultipleLoadedModels(t *testing.T) {
 	s.loadedMu.Unlock()
 }
 
-func TestRequestsModelTooBigForSystem(t *testing.T) {
-	ctx, done := context.WithTimeout(context.Background(), 500*time.Millisecond)
-	defer done()
-	s := InitScheduler(ctx)
-	s.getGpuFn = func() gpu.GpuInfoList {
-		g := gpu.GpuInfo{Library: "metal"}
-		g.TotalMemory = 4 * format.MebiByte
-		g.FreeMemory = 3 * format.MebiByte
-		return []gpu.GpuInfo{g}
-	}
-
-	s.getCpuFn = func() gpu.GpuInfoList {
-		g := gpu.GpuInfo{Library: "cpu"}
-		g.TotalMemory = 4 * format.MebiByte
-		g.FreeMemory = 2 * format.MebiByte
-		return []gpu.GpuInfo{g}
-	}
-	a := newScenarioRequest(t, ctx, "ollama-model-1", 10, &api.Duration{Duration: 5 * time.Millisecond})
-
-	s.newServerFn = a.newServer
-	slog.Info("a")
-	s.pendingReqCh <- a.req
-	require.Len(t, s.pendingReqCh, 1)
-	s.Run(ctx)
-	select {
-	case <-a.req.successCh:
-		if runtime.GOOS == "linux" {
-			t.Fatal("request should have been rejected with out of space")
-		}
-		// else - Darwin and Windows don't reject right now
-	case err := <-a.req.errCh:
-		require.Contains(t, err.Error(), "too large")
-	case <-ctx.Done():
-		t.Fatal("timeout")
-	}
-}
 func TestGetRunner(t *testing.T) {
 	ctx, done := context.WithTimeout(context.Background(), 100*time.Millisecond)
 	defer done()

From a3c20e3f181607760ee86893baaf31b3c7fd3012 Mon Sep 17 00:00:00 2001
From: Daniel Hiltgen 
Date: Mon, 22 Jul 2024 08:52:16 -0700
Subject: [PATCH 120/384] Refine error reporting for subprocess crash

On windows, the exit status winds up being the search term many
users search for and end up piling in on issues that are unrelated.
This refines the reporting so that if we have a more detailed message
we'll suppress the exit status portion of the message.
---
 llm/server.go | 21 ++++++++++++---------
 1 file changed, 12 insertions(+), 9 deletions(-)

diff --git a/llm/server.go b/llm/server.go
index ba7eab03c..08463ef01 100644
--- a/llm/server.go
+++ b/llm/server.go
@@ -417,7 +417,17 @@ func NewLlamaServer(gpus gpu.GpuInfoList, model string, ggml *GGML, adapters, pr
 
 		// reap subprocess when it exits
 		go func() {
-			s.done <- s.cmd.Wait()
+			err := s.cmd.Wait()
+			// Favor a more detailed message over the process exit status
+			if err != nil && s.status != nil && s.status.LastErrMsg != "" {
+				slog.Debug("llama runner terminated", "error", err)
+				if strings.Contains(s.status.LastErrMsg, "unknown model") {
+					s.status.LastErrMsg = "this model is not supported by your version of Ollama. You may need to upgrade"
+				}
+				s.done <- fmt.Errorf(s.status.LastErrMsg)
+			} else {
+				s.done <- err
+			}
 		}()
 
 		return s, nil
@@ -580,14 +590,7 @@ func (s *llmServer) WaitUntilRunning(ctx context.Context) error {
 			slog.Warn("client connection closed before server finished loading, aborting load")
 			return fmt.Errorf("timed out waiting for llama runner to start: %w", ctx.Err())
 		case err := <-s.done:
-			msg := ""
-			if s.status != nil && s.status.LastErrMsg != "" {
-				msg = s.status.LastErrMsg
-			}
-			if strings.Contains(msg, "unknown model") {
-				return fmt.Errorf("this model is not supported by your version of Ollama. You may need to upgrade")
-			}
-			return fmt.Errorf("llama runner process has terminated: %v %s", err, msg)
+			return fmt.Errorf("llama runner process has terminated: %w", err)
 		default:
 		}
 		if time.Now().After(stallTimer) {

From cc269ba0943ee1fa0bddcce8027d0a6d1b86fec5 Mon Sep 17 00:00:00 2001
From: Daniel Hiltgen 
Date: Mon, 22 Jul 2024 09:08:11 -0700
Subject: [PATCH 121/384] Remove no longer supported max vram var

The OLLAMA_MAX_VRAM env var was a temporary workaround for OOM
scenarios.  With Concurrency this was no longer wired up, and the simplistic
value doesn't map to multi-GPU setups.  Users can still set `num_gpu`
to limit memory usage to avoid OOM if we get our predictions wrong.
---
 cmd/cmd.go                      |  1 -
 envconfig/config.go             | 13 -------------
 integration/concurrency_test.go |  4 ++--
 3 files changed, 2 insertions(+), 16 deletions(-)

diff --git a/cmd/cmd.go b/cmd/cmd.go
index 2252a905e..b761d018f 100644
--- a/cmd/cmd.go
+++ b/cmd/cmd.go
@@ -1344,7 +1344,6 @@ func NewCLI() *cobra.Command {
 				envVars["OLLAMA_TMPDIR"],
 				envVars["OLLAMA_FLASH_ATTENTION"],
 				envVars["OLLAMA_LLM_LIBRARY"],
-				envVars["OLLAMA_MAX_VRAM"],
 			})
 		default:
 			appendEnvDocs(cmd, envs)
diff --git a/envconfig/config.go b/envconfig/config.go
index 62d661ebc..0abc69686 100644
--- a/envconfig/config.go
+++ b/envconfig/config.go
@@ -43,8 +43,6 @@ var (
 	MaxRunners int
 	// Set via OLLAMA_MAX_QUEUE in the environment
 	MaxQueuedRequests int
-	// Set via OLLAMA_MAX_VRAM in the environment
-	MaxVRAM uint64
 	// Set via OLLAMA_MODELS in the environment
 	ModelsDir string
 	// Set via OLLAMA_NOHISTORY in the environment
@@ -89,7 +87,6 @@ func AsMap() map[string]EnvVar {
 		"OLLAMA_LLM_LIBRARY":       {"OLLAMA_LLM_LIBRARY", LLMLibrary, "Set LLM library to bypass autodetection"},
 		"OLLAMA_MAX_LOADED_MODELS": {"OLLAMA_MAX_LOADED_MODELS", MaxRunners, "Maximum number of loaded models per GPU"},
 		"OLLAMA_MAX_QUEUE":         {"OLLAMA_MAX_QUEUE", MaxQueuedRequests, "Maximum number of queued requests"},
-		"OLLAMA_MAX_VRAM":          {"OLLAMA_MAX_VRAM", MaxVRAM, "Maximum VRAM"},
 		"OLLAMA_MODELS":            {"OLLAMA_MODELS", ModelsDir, "The path to the models directory"},
 		"OLLAMA_NOHISTORY":         {"OLLAMA_NOHISTORY", NoHistory, "Do not preserve readline history"},
 		"OLLAMA_NOPRUNE":           {"OLLAMA_NOPRUNE", NoPrune, "Do not prune model blobs on startup"},
@@ -194,16 +191,6 @@ func LoadConfig() {
 
 	TmpDir = clean("OLLAMA_TMPDIR")
 
-	userLimit := clean("OLLAMA_MAX_VRAM")
-	if userLimit != "" {
-		avail, err := strconv.ParseUint(userLimit, 10, 64)
-		if err != nil {
-			slog.Error("invalid setting, ignoring", "OLLAMA_MAX_VRAM", userLimit, "error", err)
-		} else {
-			MaxVRAM = avail
-		}
-	}
-
 	LLMLibrary = clean("OLLAMA_LLM_LIBRARY")
 
 	if onp := clean("OLLAMA_NUM_PARALLEL"); onp != "" {
diff --git a/integration/concurrency_test.go b/integration/concurrency_test.go
index d66ba9f00..8593285b4 100644
--- a/integration/concurrency_test.go
+++ b/integration/concurrency_test.go
@@ -69,7 +69,7 @@ func TestIntegrationConcurrentPredictOrcaMini(t *testing.T) {
 	reqLimit := len(req)
 	iterLimit := 5
 
-	vram := os.Getenv("OLLAMA_MAX_VRAM")
+	vram := os.Getenv("OLLAMA_MAX_VRAM") // TODO - discover actual VRAM
 	if vram != "" {
 		max, err := strconv.ParseUint(vram, 10, 64)
 		require.NoError(t, err)
@@ -106,7 +106,7 @@ func TestIntegrationConcurrentPredictOrcaMini(t *testing.T) {
 
 // Stress the system if we know how much VRAM it has, and attempt to load more models than will fit
 func TestMultiModelStress(t *testing.T) {
-	vram := os.Getenv("OLLAMA_MAX_VRAM")
+	vram := os.Getenv("OLLAMA_MAX_VRAM") // TODO - discover actual VRAM
 	if vram == "" {
 		t.Skip("OLLAMA_MAX_VRAM not specified, can't pick the right models for the stress test")
 	}

From b3e5491e41811294de9d81649a96581af6522d08 Mon Sep 17 00:00:00 2001
From: Jeffrey Morgan 
Date: Mon, 22 Jul 2024 12:38:03 -0400
Subject: [PATCH 122/384] server: collect nested tool call objects when parsing
 (#5824)

---
 server/model.go                   | 43 +++++++++++++++++++++--------
 server/model_test.go              |  1 +
 server/routes.go                  |  4 +--
 server/testdata/tools/xlam.gotmpl | 45 +++++++++++++++++++++++++++++++
 server/testdata/tools/xlam.out    | 40 +++++++++++++++++++++++++++
 5 files changed, 120 insertions(+), 13 deletions(-)
 create mode 100644 server/testdata/tools/xlam.gotmpl
 create mode 100644 server/testdata/tools/xlam.out

diff --git a/server/model.go b/server/model.go
index a084dd8c8..bf38c415b 100644
--- a/server/model.go
+++ b/server/model.go
@@ -344,6 +344,10 @@ func (m *Model) parseToolCalls(s string) ([]api.ToolCall, bool) {
 		}
 	}
 
+	if name == "" || arguments == "" {
+		return nil, false
+	}
+
 	var objs []map[string]any
 	for offset := 0; offset < len(s); {
 		var obj map[string]any
@@ -361,23 +365,40 @@ func (m *Model) parseToolCalls(s string) ([]api.ToolCall, bool) {
 			return nil, false
 		} else {
 			offset += int(decoder.InputOffset())
-			objs = append(objs, obj)
+
+			// collect all nested objects
+			var collect func(any) []map[string]any
+			collect = func(obj any) (all []map[string]any) {
+				switch o := obj.(type) {
+				case map[string]any:
+					all = append(all, o)
+					for _, v := range o {
+						all = append(all, collect(v)...)
+					}
+				case []any:
+					for _, v := range o {
+						all = append(all, collect(v)...)
+					}
+				}
+
+				return all
+			}
+			objs = append(objs, collect(obj)...)
 		}
 	}
 
 	var toolCalls []api.ToolCall
 	for _, kv := range objs {
-		var call api.ToolCall
-		for k, v := range kv {
-			switch k {
-			case name:
-				call.Function.Name = v.(string)
-			case arguments:
-				call.Function.Arguments = v.(map[string]any)
-			}
+		n, nok := kv[name].(string)
+		a, aok := kv[arguments].(map[string]any)
+		if nok && aok {
+			toolCalls = append(toolCalls, api.ToolCall{
+				Function: api.ToolCallFunction{
+					Name:      n,
+					Arguments: a,
+				},
+			})
 		}
-
-		toolCalls = append(toolCalls, call)
 	}
 
 	return toolCalls, len(toolCalls) > 0
diff --git a/server/model_test.go b/server/model_test.go
index 7c826b066..5829adfce 100644
--- a/server/model_test.go
+++ b/server/model_test.go
@@ -166,6 +166,7 @@ The temperature in San Francisco, CA is 70°F and in Toronto, Canada is 20°C.`,
 {"name": "get_current_weather", "arguments": {"format":"fahrenheit","location":"San Francisco, CA"}}
 {"name": "get_current_weather", "arguments": {"format":"celsius","location":"Toronto, Canada"}}
 `, true},
+		{"xlam", `{"tool_calls": [{"name": "get_current_weather", "arguments": {"format":"fahrenheit","location":"San Francisco, CA"}},{"name": "get_current_weather", "arguments": {"format":"celsius","location":"Toronto, Canada"}}]}`, true},
 	}
 
 	var tools []api.Tool
diff --git a/server/routes.go b/server/routes.go
index 85db79249..0d7ca003c 100644
--- a/server/routes.go
+++ b/server/routes.go
@@ -611,10 +611,10 @@ func (s *Server) CreateModelHandler(c *gin.Context) {
 		quantization := cmp.Or(r.Quantize, r.Quantization)
 		if err := CreateModel(ctx, name, filepath.Dir(r.Path), strings.ToUpper(quantization), f, fn); err != nil {
 			if errors.Is(err, errBadTemplate) {
-			  ch <- gin.H{"error": err.Error(), "status": http.StatusBadRequest}
+				ch <- gin.H{"error": err.Error(), "status": http.StatusBadRequest}
 			}
 			ch <- gin.H{"error": err.Error()}
-		  }
+		}
 	}()
 
 	if r.Stream != nil && !*r.Stream {
diff --git a/server/testdata/tools/xlam.gotmpl b/server/testdata/tools/xlam.gotmpl
new file mode 100644
index 000000000..51513d698
--- /dev/null
+++ b/server/testdata/tools/xlam.gotmpl
@@ -0,0 +1,45 @@
+{{- if .System }}{{ .System }}
+{{ end }}
+{{- range $i, $_ := .Messages }}
+{{- if eq .Role "user" }}### Instruction:
+{{- if and $.Tools (le (len (slice $.Messages $i)) 2) }}
+[BEGIN OF TASK INSTRUCTION]
+You are an expert in composing functions. You are given a question and a set of possible functions. 
+Based on the question, you will need to make one or more function/tool calls to achieve the purpose. 
+If none of the functions can be used, point it out and refuse to answer. 
+If the given question lacks the parameters required by the function, also point it out.
+[END OF TASK INSTRUCTION]
+
+[BEGIN OF AVAILABLE TOOLS]
+{{ $.Tools }}
+[END OF AVAILABLE TOOLS]
+
+[BEGIN OF FORMAT INSTRUCTION]
+The output MUST strictly adhere to the following JSON format, and NO other text MUST be included.
+The example format is as follows. Please make sure the parameter type is correct. If no function call is needed, please make tool_calls an empty list '[]'.
+```
+{
+    "tool_calls": [
+    {"name": "func_name1", "arguments": {"argument1": "value1", "argument2": "value2"}},
+    ... (more tool calls as required)
+    ]
+}
+```
+[END OF FORMAT INSTRUCTION]
+
+[BEGIN OF QUERY]
+{{ .Content }}
+[END OF QUERY]
+
+
+{{ else }}
+{{ .Content }}
+{{ end }}
+{{- else if .ToolCalls }}### Response:
+{"tool_calls": [{{ range .ToolCalls }}{"name": "{{ .Function.Name }}", "arguments": {{ .Function.Arguments }}}{{ end }}]}
+<|EOT|>
+{{ else if eq .Role "assistant" }}### Response:
+{{ .Content }}
+<|EOT|>
+{{ end }}
+{{- end }}### Response:
\ No newline at end of file
diff --git a/server/testdata/tools/xlam.out b/server/testdata/tools/xlam.out
new file mode 100644
index 000000000..a4a9952fc
--- /dev/null
+++ b/server/testdata/tools/xlam.out
@@ -0,0 +1,40 @@
+You are a knowledgable assistant. You can answer questions and perform tasks.
+### Instruction:
+What's the weather like today in Paris?
+### Response:
+{"tool_calls": [{"name": "get_current_weather", "arguments": {"format":"celsius","location":"Paris, France"}}]}
+<|EOT|>
+### Response:
+The current temperature in Paris, France is 22 degrees Celsius.
+<|EOT|>
+### Instruction:
+[BEGIN OF TASK INSTRUCTION]
+You are an expert in composing functions. You are given a question and a set of possible functions. 
+Based on the question, you will need to make one or more function/tool calls to achieve the purpose. 
+If none of the functions can be used, point it out and refuse to answer. 
+If the given question lacks the parameters required by the function, also point it out.
+[END OF TASK INSTRUCTION]
+
+[BEGIN OF AVAILABLE TOOLS]
+[{"type":"function","function":{"name":"get_current_weather","description":"Get the current weather","parameters":{"type":"object","required":["location","format"],"properties":{"format":{"type":"string","description":"The temperature unit to use. Infer this from the users location.","enum":["celsius","fahrenheit"]},"location":{"type":"string","description":"The city and state, e.g. San Francisco, CA"}}}}}]
+[END OF AVAILABLE TOOLS]
+
+[BEGIN OF FORMAT INSTRUCTION]
+The output MUST strictly adhere to the following JSON format, and NO other text MUST be included.
+The example format is as follows. Please make sure the parameter type is correct. If no function call is needed, please make tool_calls an empty list '[]'.
+```
+{
+    "tool_calls": [
+    {"name": "func_name1", "arguments": {"argument1": "value1", "argument2": "value2"}},
+    ... (more tool calls as required)
+    ]
+}
+```
+[END OF FORMAT INSTRUCTION]
+
+[BEGIN OF QUERY]
+What's the weather like today in San Francisco and Toronto?
+[END OF QUERY]
+
+
+### Response:
\ No newline at end of file

From f8fedbda20b1b2531499ba64758642b0568b6f01 Mon Sep 17 00:00:00 2001
From: Jeffrey Morgan 
Date: Mon, 22 Jul 2024 12:42:00 -0400
Subject: [PATCH 123/384] Update llama.cpp submodule commit to `d94c6e0c`
 (#5805)

---
 llm/llama.cpp                                 |   2 +-
 llm/patches/05-default-pretokenizer.diff      |  10 +-
 ...{07-embeddings.diff => 06-embeddings.diff} |   0
 llm/patches/06-qwen2.diff                     |  13 -
 ...clip-unicode.diff => 07-clip-unicode.diff} |   0
 .../{09-pooling.diff => 08-pooling.diff}      |   0
 llm/patches/09-lora.diff                      | 360 ++++++++++++++++++
 llm/patches/10-tekken.diff                    |  43 ---
 llm/patches/11-embd_kv.diff                   |  19 -
 9 files changed, 366 insertions(+), 81 deletions(-)
 rename llm/patches/{07-embeddings.diff => 06-embeddings.diff} (100%)
 delete mode 100644 llm/patches/06-qwen2.diff
 rename llm/patches/{08-clip-unicode.diff => 07-clip-unicode.diff} (100%)
 rename llm/patches/{09-pooling.diff => 08-pooling.diff} (100%)
 create mode 100644 llm/patches/09-lora.diff
 delete mode 100644 llm/patches/10-tekken.diff
 delete mode 100644 llm/patches/11-embd_kv.diff

diff --git a/llm/llama.cpp b/llm/llama.cpp
index a8db2a9ce..d94c6e0cc 160000
--- a/llm/llama.cpp
+++ b/llm/llama.cpp
@@ -1 +1 @@
-Subproject commit a8db2a9ce64cd4417f6a312ab61858f17f0f8584
+Subproject commit d94c6e0ccbd29ee1ba4f44e9caa8682ad94df9fa
diff --git a/llm/patches/05-default-pretokenizer.diff b/llm/patches/05-default-pretokenizer.diff
index 341a6f590..646bc49c3 100644
--- a/llm/patches/05-default-pretokenizer.diff
+++ b/llm/patches/05-default-pretokenizer.diff
@@ -1,8 +1,8 @@
 diff --git a/src/llama.cpp b/src/llama.cpp
-index 2b9ace28..172640e2 100644
+index 8fe51971..7113ba64 100644
 --- a/src/llama.cpp
 +++ b/src/llama.cpp
-@@ -5357,16 +5357,7 @@ static void llm_load_vocab(
+@@ -5433,16 +5433,7 @@ static void llm_load_vocab(
          if (vocab.type == LLAMA_VOCAB_TYPE_BPE) {
              vocab.tokenizer_add_space_prefix = false;
              vocab.tokenizer_clean_spaces = true;
@@ -20,9 +20,9 @@ index 2b9ace28..172640e2 100644
                  vocab.type_pre = LLAMA_VOCAB_PRE_TYPE_DEFAULT;
              } else if (
                      tokenizer_pre == "llama3"   ||
-@@ -5439,7 +5430,8 @@ static void llm_load_vocab(
-                 tokenizer_pre == "jais") {
-                 vocab.type_pre = LLAMA_VOCAB_PRE_TYPE_JAIS;
+@@ -5526,7 +5517,8 @@ static void llm_load_vocab(
+                 vocab.type_pre = LLAMA_VOCAB_PRE_TYPE_SMOLLM;
+                 vocab.tokenizer_clean_spaces = false;
              } else {
 -                throw std::runtime_error(format("unknown pre-tokenizer type: '%s'", tokenizer_pre.c_str()));
 +                LLAMA_LOG_WARN("%s: missing or unrecognized pre-tokenizer type, using: 'default'\n", __func__);
diff --git a/llm/patches/07-embeddings.diff b/llm/patches/06-embeddings.diff
similarity index 100%
rename from llm/patches/07-embeddings.diff
rename to llm/patches/06-embeddings.diff
diff --git a/llm/patches/06-qwen2.diff b/llm/patches/06-qwen2.diff
deleted file mode 100644
index 1c7109f6f..000000000
--- a/llm/patches/06-qwen2.diff
+++ /dev/null
@@ -1,13 +0,0 @@
-diff --git a/src/llama.cpp b/src/llama.cpp
-index 40d2ec2c..f34eb79a 100644
---- a/src/llama.cpp
-+++ b/src/llama.cpp
-@@ -6943,7 +6943,7 @@ static struct ggml_tensor * llm_build_kqv(
-         struct ggml_tensor * kq = ggml_mul_mat(ctx, k, q);
-         cb(kq, "kq", il);
- 
--        if (model.arch == LLM_ARCH_PHI2 || model.arch == LLM_ARCH_PHI3 || model.arch == LLM_ARCH_GPTNEOX) {
-+        if (model.arch == LLM_ARCH_PHI2 || model.arch == LLM_ARCH_PHI3 || model.arch == LLM_ARCH_GPTNEOX || model.arch == LLM_ARCH_QWEN2) {
-             // for this arch, we need to perform the KQ multiplication with F32 precision, otherwise we get NaNs
-             // ref: https://github.com/ggerganov/llama.cpp/pull/4490#issuecomment-1859055847
-             ggml_mul_mat_set_prec(kq, GGML_PREC_F32);
diff --git a/llm/patches/08-clip-unicode.diff b/llm/patches/07-clip-unicode.diff
similarity index 100%
rename from llm/patches/08-clip-unicode.diff
rename to llm/patches/07-clip-unicode.diff
diff --git a/llm/patches/09-pooling.diff b/llm/patches/08-pooling.diff
similarity index 100%
rename from llm/patches/09-pooling.diff
rename to llm/patches/08-pooling.diff
diff --git a/llm/patches/09-lora.diff b/llm/patches/09-lora.diff
new file mode 100644
index 000000000..fc1017a65
--- /dev/null
+++ b/llm/patches/09-lora.diff
@@ -0,0 +1,360 @@
+diff --git a/common/common.cpp b/common/common.cpp
+index dbb724fb..c26fe6ee 100644
+--- a/common/common.cpp
++++ b/common/common.cpp
+@@ -2087,14 +2087,29 @@ std::tuple llama_init_from_gpt_par
+     for (unsigned int i = 0; i < params.lora_adapter.size(); ++i) {
+         const std::string & lora_adapter = std::get<0>(params.lora_adapter[i]);
+         float lora_scale = std::get<1>(params.lora_adapter[i]);
++
++        // try to load as gguf
+         auto adapter = llama_lora_adapter_init(model, lora_adapter.c_str());
+         if (adapter == nullptr) {
+-            fprintf(stderr, "%s: error: failed to apply lora adapter\n", __func__);
+-            llama_free(lctx);
+-            llama_free_model(model);
+-            return std::make_tuple(nullptr, nullptr);
++            fprintf(stderr, "%s: error: failed to apply lora adapter, trying ggla\n", __func__);
++
++            // if that fails, try loading as ggla for compatibility
++            int err = llama_model_apply_lora_from_file(model,
++                                                    lora_adapter.c_str(),
++                                                    lora_scale,
++                                                    ((i > 0) || params.lora_base.empty())
++                                                        ? NULL
++                                                        : params.lora_base.c_str(),
++                                                    params.n_threads);
++            if (err != 0) {
++                fprintf(stderr, "%s: error: failed to apply lora adapter\n", __func__);
++                llama_free(lctx);
++                llama_free_model(model);
++                return std::make_tuple(nullptr, nullptr);
++            }
++        } else {
++            llama_lora_adapter_set(lctx, adapter, lora_scale);
+         }
+-        llama_lora_adapter_set(lctx, adapter, lora_scale);
+     }
+ 
+     if (params.ignore_eos) {
+diff --git a/include/llama.h b/include/llama.h
+index 93fd77ca..b0fb37a6 100644
+--- a/include/llama.h
++++ b/include/llama.h
+@@ -1160,6 +1160,20 @@ extern "C" {
+ 
+     LLAMA_API void llama_dump_timing_info_yaml(FILE * stream, const struct llama_context * ctx);
+ 
++    // Apply a LoRA adapter to a loaded model
++    // path_base_model is the path to a higher quality model to use as a base for
++    // the layers modified by the adapter. Can be NULL to use the current loaded model.
++    // The model needs to be reloaded before applying a new adapter, otherwise the adapter
++    // will be applied on top of the previous one
++    // Returns 0 on success
++    LLAMA_API int32_t llama_model_apply_lora_from_file(
++            const struct llama_model * model,
++                            const char * path_lora,
++                                float   scale,
++                            const char * path_base_model,
++                                int32_t   n_threads);
++
++
+ #ifdef __cplusplus
+ }
+ #endif
+diff --git a/src/llama.cpp b/src/llama.cpp
+index 80a0dd0f..9d7b0e17 100644
+--- a/src/llama.cpp
++++ b/src/llama.cpp
+@@ -21880,3 +21880,290 @@ static void llama_log_callback_default(ggml_log_level level, const char * text,
+     fputs(text, stderr);
+     fflush(stderr);
+ }
++
++static int llama_apply_lora_from_file_internal(
++    const struct llama_model & model, const char * path_lora, float scale, const char * path_base_model, int n_threads
++) {
++    LLAMA_LOG_INFO("%s: applying lora adapter from '%s' - please wait ...\n", __func__, path_lora);
++
++    const int64_t t_start_lora_us = ggml_time_us();
++
++    llama_file fin(path_lora, "rb");
++
++    // verify magic and version
++    {
++        uint32_t magic = fin.read_u32();
++        if (magic != LLAMA_FILE_MAGIC_GGLA) {
++            LLAMA_LOG_ERROR("%s: bad file magic\n", __func__);
++            return 1;
++        }
++
++        uint32_t format_version = fin.read_u32();
++        if (format_version != 1) {
++            LLAMA_LOG_ERROR("%s: unsupported file version\n", __func__ );
++            return 1;
++        }
++    }
++
++    int32_t lora_r = fin.read_u32();
++    int32_t lora_alpha = fin.read_u32();
++    float scaling = scale * (float)lora_alpha / (float)lora_r;
++
++    LLAMA_LOG_INFO("%s: r = %d, alpha = %d, scaling = %.2f\n", __func__, lora_r, lora_alpha, scaling);
++
++    // load base model
++    std::unique_ptr ml;
++    if (path_base_model) {
++        LLAMA_LOG_INFO("%s: loading base model from '%s'\n", __func__, path_base_model);
++        ml.reset(new llama_model_loader(path_base_model, /*use_mmap*/ true, /*check_tensors*/ false, /*kv_overrides*/ nullptr));
++        ml->init_mappings(/*prefetch*/ false); // no prefetching
++    }
++
++    struct tensor_meta {
++        std::string name;
++        ggml_type type;
++        int32_t ne[2];
++        size_t offset;
++    };
++    std::map tensor_meta_map;
++
++    // load all tensor meta
++    while (true) {
++        if (fin.tell() == fin.size) {
++            // eof
++            break;
++        }
++
++        int32_t n_dims;
++        int32_t name_len;
++        int32_t ftype;
++
++        fin.read_raw(&n_dims, sizeof(n_dims));
++        fin.read_raw(&name_len, sizeof(name_len));
++        fin.read_raw(&ftype, sizeof(ftype));
++
++        if (n_dims != 1 && n_dims != 2) {
++            LLAMA_LOG_ERROR("%s: unsupported tensor dimension %d\n", __func__, n_dims);
++            return 1;
++        }
++
++        int32_t ne[2] = { 1, 1 };
++        for (int i = 0; i < n_dims; ++i) {
++            fin.read_raw(&ne[i], sizeof(ne[i]));
++        }
++
++        std::string name;
++        {
++            GGML_ASSERT(name_len < GGML_MAX_NAME);
++            char buf[GGML_MAX_NAME];
++            fin.read_raw(buf, name_len);
++            name = std::string(buf, name_len);
++        }
++
++        // check for lora suffix
++        std::string lora_suffix;
++        if (name.length() > 6) {
++            lora_suffix = name.substr(name.length() - 6);
++        }
++        if (lora_suffix != ".loraA" && lora_suffix != ".loraB") {
++            LLAMA_LOG_ERROR("%s: error: '%s' is not a lora tensor\n", __func__, name.c_str());
++            return 1;
++        }
++
++        // tensor type
++        ggml_type wtype;
++        switch (ftype) {
++            case 0: wtype = GGML_TYPE_F32;  break;
++            case 1: wtype = GGML_TYPE_F16;  break;
++            default:
++                    {
++                        LLAMA_LOG_ERROR("%s: invalid tensor data type '%d'\n",
++                                __func__, ftype);
++                        return 1;
++                    }
++        }
++
++        // data offset
++        size_t offset = fin.tell();
++        offset = (offset + 31) & -32;
++
++        // skip tensor data
++        fin.seek(offset + ggml_row_size(wtype, ne[0]) * ne[1], SEEK_SET);
++
++        tensor_meta_map.emplace(name, tensor_meta{ name, wtype, { ne[0], ne[1] }, offset });
++    }
++
++    bool warned = false;
++    int n_tensors = 0;
++
++    // apply
++    ggml_backend_t backend_cpu = ggml_backend_cpu_init();
++    if (backend_cpu == nullptr) {
++        LLAMA_LOG_ERROR("%s: error: failed to initialize cpu backend\n", __func__);
++        return 1;
++    }
++    ggml_backend_cpu_set_n_threads(backend_cpu, n_threads);
++
++    std::vector> read_buf;
++    for (const auto & it : model.tensors_by_name) {
++        const std::string & base_name = it.first;
++        ggml_tensor * model_t = it.second;
++
++        if (tensor_meta_map.find(base_name + ".loraA") == tensor_meta_map.end() ||
++            tensor_meta_map.find(base_name + ".loraB") == tensor_meta_map.end()) {
++            continue;
++        }
++
++        tensor_meta & metaA = tensor_meta_map.at(base_name + ".loraA");
++        tensor_meta & metaB = tensor_meta_map.at(base_name + ".loraB");
++
++        ggml_init_params lora_init_params = {
++            /* .mem_size   */ ggml_tensor_overhead()*128 + ggml_graph_overhead(),
++            /* .mem_buffer */ nullptr,
++            /* .no_alloc   */ true,
++        };
++        ggml_context * lora_ctx = ggml_init(lora_init_params);
++        if (lora_ctx == nullptr) {
++            LLAMA_LOG_ERROR("%s: error: failed to initialize lora context\n", __func__);
++            ggml_backend_free(backend_cpu);
++            return 1;
++        }
++
++        // create tensors
++        ggml_tensor * loraA = ggml_new_tensor_2d(lora_ctx, metaA.type, metaA.ne[0], metaA.ne[1]);
++        ggml_tensor * loraB = ggml_new_tensor_2d(lora_ctx, metaB.type, metaB.ne[0], metaB.ne[1]);
++        ggml_set_name(loraA, metaA.name.c_str());
++        ggml_set_name(loraB, metaB.name.c_str());
++
++        ggml_tensor * base_t;
++        if (ml) {
++            if (!ml->get_tensor_meta(base_name.c_str())) {
++                LLAMA_LOG_ERROR("%s: error: tensor '%s' not found in base model\n", __func__, base_name.c_str());
++                return 1;
++            }
++            base_t = ggml_dup_tensor(lora_ctx, ml->get_tensor_meta(base_name.c_str()));
++        } else {
++            base_t = ggml_dup_tensor(lora_ctx, model_t);
++        }
++        ggml_set_name(base_t, base_name.c_str());
++
++        // allocate in backend buffer
++        ggml_backend_buffer_t lora_buf = ggml_backend_alloc_ctx_tensors_from_buft(lora_ctx, ggml_backend_cpu_buffer_type());
++        if (lora_buf == nullptr) {
++            LLAMA_LOG_ERROR("%s: error: failed to allocate lora tensors\n", __func__);
++            return 1;
++        }
++
++        // load tensor data
++        auto load_tensor = [&read_buf, &fin](const tensor_meta & tensor_meta, ggml_tensor * tensor) {
++            read_buf.resize(ggml_nbytes(tensor));
++            fin.seek(tensor_meta.offset, SEEK_SET);
++            fin.read_raw(read_buf.data(), ggml_nbytes(tensor));
++            ggml_backend_tensor_set(tensor, read_buf.data(), 0, read_buf.size());
++        };
++        load_tensor(metaA, loraA);
++        load_tensor(metaB, loraB);
++
++        // load base model tensor data
++        if (ml) {
++            ml->load_data_for(base_t);
++        } else {
++            ggml_backend_tensor_copy(model_t, base_t);
++        }
++
++        if (ggml_is_quantized(base_t->type) && !warned) {
++            LLAMA_LOG_WARN("%s: warning: using a lora adapter with a quantized model may result in poor quality, "
++                            "use a f16 or f32 base model with --lora-base\n", __func__);
++            warned = true;
++        }
++
++        if (base_t->ne[0] != loraA->ne[1] || base_t->ne[1] != loraB->ne[1]) {
++            LLAMA_LOG_ERROR("%s: incompatible tensor dimensions (%" PRId64 " and %" PRId64 ");"
++                            " are you sure that this adapter is for this model?\n", __func__, base_t->ne[0], loraA->ne[1]);
++            ggml_free(lora_ctx);
++            ggml_backend_buffer_free(lora_buf);
++            ggml_backend_free(backend_cpu);
++            return 1;
++        }
++
++        auto build_lora_graph = [&]() {
++            // w = w + BA*s
++            ggml_tensor * BA = ggml_mul_mat(lora_ctx, loraA, loraB);
++            ggml_set_name(BA, "BA");
++
++            if (scaling != 1.0f) {
++                BA = ggml_scale(lora_ctx, BA, scaling);
++                ggml_set_name(BA, "BA_scaled");
++            }
++
++            ggml_tensor * r;
++            r = ggml_add_inplace(lora_ctx, base_t, BA);
++            ggml_set_name(r, "r_add");
++
++            if (base_t->type != model_t->type) {
++                // convert the result to the model type
++                r = ggml_cast(lora_ctx, r, model_t->type);
++                ggml_set_name(r, "r_cast");
++            }
++
++            return r;
++        };
++
++        ggml_cgraph * gf = ggml_new_graph(lora_ctx);
++        ggml_tensor * r = build_lora_graph();
++        ggml_build_forward_expand(gf, r);
++
++        ggml_backend_buffer_t graph_buf = ggml_backend_alloc_ctx_tensors_from_buft(lora_ctx, ggml_backend_cpu_buffer_type());
++        if (graph_buf == nullptr) {
++            LLAMA_LOG_ERROR("%s: error: failed to allocate graph tensors\n", __func__);
++            ggml_free(lora_ctx);
++            ggml_backend_buffer_free(lora_buf);
++            ggml_backend_free(backend_cpu);
++            return 1;
++        }
++
++        ggml_backend_graph_compute(backend_cpu, gf);
++
++        ggml_backend_tensor_set(model_t, r->data, 0, ggml_nbytes(r));
++
++#if 0
++        // TODO: use scheduler with fallback to CPU for less copies between CPU and GPU
++        //ggml_backend_sched_t sched = ggml_backend_sched_new(backends.data(), backends.size(), GGML_DEFAULT_GRAPH_SIZE);
++
++        // sched compute
++        ggml_build_forward_expand(gf, build_graph());
++        ggml_backend_sched_init_measure(sched, gf);
++
++        // create the graph again, since the previous one was destroyed by the measure
++        ggml_graph_clear(gf);
++        ggml_build_forward_expand(gf, build_graph());
++        ggml_backend_sched_graph_compute(sched, gf);
++        ggml_backend_sched_free(sched);
++#endif
++
++        ggml_backend_buffer_free(lora_buf);
++        ggml_backend_buffer_free(graph_buf);
++        ggml_free(lora_ctx);
++
++        n_tensors++;
++        if (n_tensors % 4 == 0) {
++            LLAMA_LOG_INFO(".");
++        }
++    }
++
++    ggml_backend_free(backend_cpu);
++
++    const int64_t t_lora_us = ggml_time_us() - t_start_lora_us;
++    LLAMA_LOG_INFO(" done (%.2f ms)\n", t_lora_us / 1000.0);
++
++    return 0;
++}
++
++int32_t llama_model_apply_lora_from_file(const struct llama_model * model, const char * path_lora, float scale, const char * path_base_model, int32_t n_threads) {
++    try {
++        return llama_apply_lora_from_file_internal(*model, path_lora, scale, path_base_model, n_threads);
++    } catch (const std::exception & err) {
++        LLAMA_LOG_ERROR("%s: failed to apply lora adapter: %s\n", __func__, err.what());
++        return 1;
++    }
++}
+\ No newline at end of file
diff --git a/llm/patches/10-tekken.diff b/llm/patches/10-tekken.diff
deleted file mode 100644
index 56a583e0c..000000000
--- a/llm/patches/10-tekken.diff
+++ /dev/null
@@ -1,43 +0,0 @@
-diff --git a/include/llama.h b/include/llama.h
-index bb4b05ba..a92174e0 100644
---- a/include/llama.h
-+++ b/include/llama.h
-@@ -92,6 +92,7 @@ extern "C" {
-         LLAMA_VOCAB_PRE_TYPE_CHATGLM4       = 17,
-         LLAMA_VOCAB_PRE_TYPE_VIKING         = 18,
-         LLAMA_VOCAB_PRE_TYPE_JAIS           = 19,
-+        LLAMA_VOCAB_PRE_TYPE_TEKKEN         = 20,
-     };
- 
-     // note: these values should be synchronized with ggml_rope
-diff --git a/src/llama.cpp b/src/llama.cpp
-index 18364976..435b6fe5 100644
---- a/src/llama.cpp
-+++ b/src/llama.cpp
-@@ -5429,6 +5429,12 @@ static void llm_load_vocab(
-             } else if (
-                 tokenizer_pre == "jais") {
-                 vocab.type_pre = LLAMA_VOCAB_PRE_TYPE_JAIS;
-+            } else if (
-+                tokenizer_pre == "tekken") {
-+                vocab.type_pre = LLAMA_VOCAB_PRE_TYPE_TEKKEN;
-+                vocab.tokenizer_clean_spaces = false;
-+                vocab.tokenizer_ignore_merges = true;
-+                vocab.tokenizer_add_bos = true;
-             } else {
-                 LLAMA_LOG_WARN("%s: missing or unrecognized pre-tokenizer type, using: 'default'\n", __func__);
-                 vocab.type_pre = LLAMA_VOCAB_PRE_TYPE_DEFAULT;
-@@ -15448,6 +15454,13 @@ struct llm_tokenizer_bpe {
-                     " ?[^(\\s|.,!?…。,、।۔،)]+",
-                 };
-                 break;
-+            case LLAMA_VOCAB_PRE_TYPE_TEKKEN:
-+                    // original regex from tokenizer.json
-+                    // "[^\\r\\n\\p{L}\\p{N}]?[\\p{Lu}\\p{Lt}\\p{Lm}\\p{Lo}\\p{M}]*[\\p{Ll}\\p{Lm}\\p{Lo}\\p{M}]+|[^\\r\\n\\p{L}\\p{N}]?[\\p{Lu}\\p{Lt}\\p{Lm}\\p{Lo}\\p{M}]+[\\p{Ll}\\p{Lm}\\p{Lo}\\p{M}]*|\\p{N}| ?[^\\s\\p{L}\\p{N}]+[\\r\\n/]*|\\s*[\\r\\n]+|\\s+(?!\\S)|\\s+"
-+                regex_exprs = {
-+                    "[^\\r\\n\\p{L}\\p{N}]?((?=[\\p{L}])([^a-z]))*((?=[\\p{L}])([^A-Z]))+|[^\\r\\n\\p{L}\\p{N}]?((?=[\\p{L}])([^a-z]))+((?=[\\p{L}])([^A-Z]))*|\\p{N}| ?[^\\s\\p{L}\\p{N}]+[\\r\\n/]*|\\s*[\\r\\n]+|\\s+(?!\\S)|\\s+",
-+                };
-+                break;
-             default:
-                 // default regex for BPE tokenization pre-processing
-                 regex_exprs = {
diff --git a/llm/patches/11-embd_kv.diff b/llm/patches/11-embd_kv.diff
deleted file mode 100644
index ad17a700d..000000000
--- a/llm/patches/11-embd_kv.diff
+++ /dev/null
@@ -1,19 +0,0 @@
-diff --git a/src/llama.cpp b/src/llama.cpp
-index 2b9ace28..e60d3d8d 100644
---- a/src/llama.cpp
-+++ b/src/llama.cpp
-@@ -6052,10 +6052,10 @@ static bool llm_load_tensors(
- 
-                         layer.attn_norm = ml.create_tensor(ctx_layer, tn(LLM_TENSOR_ATTN_NORM, "weight", i), {n_embd});
- 
--                        layer.wq = ml.create_tensor(ctx_split, tn(LLM_TENSOR_ATTN_Q,   "weight", i), {n_embd, n_embd});
--                        layer.wk = ml.create_tensor(ctx_split, tn(LLM_TENSOR_ATTN_K,   "weight", i), {n_embd, n_embd_gqa});
--                        layer.wv = ml.create_tensor(ctx_split, tn(LLM_TENSOR_ATTN_V,   "weight", i), {n_embd, n_embd_gqa});
--                        layer.wo = ml.create_tensor(ctx_split, tn(LLM_TENSOR_ATTN_OUT, "weight", i), {n_embd, n_embd});
-+                        layer.wq = ml.create_tensor(ctx_split, tn(LLM_TENSOR_ATTN_Q,   "weight", i), {n_embd,  n_embd_head_k * n_head});
-+                        layer.wk = ml.create_tensor(ctx_split, tn(LLM_TENSOR_ATTN_K,   "weight", i), {n_embd, n_embd_k_gqa});
-+                        layer.wv = ml.create_tensor(ctx_split, tn(LLM_TENSOR_ATTN_V,   "weight", i), {n_embd, n_embd_v_gqa});
-+                        layer.wo = ml.create_tensor(ctx_split, tn(LLM_TENSOR_ATTN_OUT, "weight", i), {n_embd_head_k * n_head, n_embd});
- 
-                         // optional bias tensors
-                         layer.bq = ml.create_tensor(ctx_layer, tn(LLM_TENSOR_ATTN_Q,   "bias", i), {n_embd},     llama_model_loader::TENSOR_NOT_REQUIRED);

From 35b89b2eaba4ac6fc4ae1ba4bf2ec6c8bafe9529 Mon Sep 17 00:00:00 2001
From: Michael Yang 
Date: Wed, 3 Jul 2024 16:00:54 -0700
Subject: [PATCH 124/384] rfc: dynamic environ lookup

---
 app/lifecycle/logging.go |  2 +-
 envconfig/config.go      | 28 ++++++++++++++++------------
 envconfig/config_test.go | 13 ++++++-------
 gpu/gpu.go               |  2 +-
 llm/memory_test.go       |  4 ++--
 llm/server.go            |  4 ++--
 server/routes.go         |  2 +-
 7 files changed, 29 insertions(+), 26 deletions(-)

diff --git a/app/lifecycle/logging.go b/app/lifecycle/logging.go
index a8f1f7cdf..3672aad59 100644
--- a/app/lifecycle/logging.go
+++ b/app/lifecycle/logging.go
@@ -14,7 +14,7 @@ import (
 func InitLogging() {
 	level := slog.LevelInfo
 
-	if envconfig.Debug {
+	if envconfig.Debug() {
 		level = slog.LevelDebug
 	}
 
diff --git a/envconfig/config.go b/envconfig/config.go
index 0abc69686..426507be0 100644
--- a/envconfig/config.go
+++ b/envconfig/config.go
@@ -26,11 +26,24 @@ func (o OllamaHost) String() string {
 
 var ErrInvalidHostPort = errors.New("invalid port specified in OLLAMA_HOST")
 
+// Debug returns true if the OLLAMA_DEBUG environment variable is set to a truthy value.
+func Debug() bool {
+	if s := clean("OLLAMA_DEBUG"); s != "" {
+		b, err := strconv.ParseBool(s)
+		if err != nil {
+			// non-empty value is truthy
+			return true
+		}
+
+		return b
+	}
+
+	return false
+}
+
 var (
 	// Set via OLLAMA_ORIGINS in the environment
 	AllowOrigins []string
-	// Set via OLLAMA_DEBUG in the environment
-	Debug bool
 	// Experimental flash attention
 	FlashAttention bool
 	// Set via OLLAMA_HOST in the environment
@@ -80,7 +93,7 @@ type EnvVar struct {
 
 func AsMap() map[string]EnvVar {
 	ret := map[string]EnvVar{
-		"OLLAMA_DEBUG":             {"OLLAMA_DEBUG", Debug, "Show additional debug information (e.g. OLLAMA_DEBUG=1)"},
+		"OLLAMA_DEBUG":             {"OLLAMA_DEBUG", Debug(), "Show additional debug information (e.g. OLLAMA_DEBUG=1)"},
 		"OLLAMA_FLASH_ATTENTION":   {"OLLAMA_FLASH_ATTENTION", FlashAttention, "Enabled flash attention"},
 		"OLLAMA_HOST":              {"OLLAMA_HOST", Host, "IP Address for the ollama server (default 127.0.0.1:11434)"},
 		"OLLAMA_KEEP_ALIVE":        {"OLLAMA_KEEP_ALIVE", KeepAlive, "The duration that models stay loaded in memory (default \"5m\")"},
@@ -137,15 +150,6 @@ func init() {
 }
 
 func LoadConfig() {
-	if debug := clean("OLLAMA_DEBUG"); debug != "" {
-		d, err := strconv.ParseBool(debug)
-		if err == nil {
-			Debug = d
-		} else {
-			Debug = true
-		}
-	}
-
 	if fa := clean("OLLAMA_FLASH_ATTENTION"); fa != "" {
 		d, err := strconv.ParseBool(fa)
 		if err == nil {
diff --git a/envconfig/config_test.go b/envconfig/config_test.go
index a5d73fd7c..f083bb03c 100644
--- a/envconfig/config_test.go
+++ b/envconfig/config_test.go
@@ -12,16 +12,15 @@ import (
 )
 
 func TestConfig(t *testing.T) {
-	Debug = false // Reset whatever was loaded in init()
 	t.Setenv("OLLAMA_DEBUG", "")
-	LoadConfig()
-	require.False(t, Debug)
+	require.False(t, Debug())
+
 	t.Setenv("OLLAMA_DEBUG", "false")
-	LoadConfig()
-	require.False(t, Debug)
+	require.False(t, Debug())
+
 	t.Setenv("OLLAMA_DEBUG", "1")
-	LoadConfig()
-	require.True(t, Debug)
+	require.True(t, Debug())
+
 	t.Setenv("OLLAMA_FLASH_ATTENTION", "1")
 	LoadConfig()
 	require.True(t, FlashAttention)
diff --git a/gpu/gpu.go b/gpu/gpu.go
index 6e25cb46d..1815668fd 100644
--- a/gpu/gpu.go
+++ b/gpu/gpu.go
@@ -611,7 +611,7 @@ func LoadOneapiMgmt(oneapiLibPaths []string) (int, *C.oneapi_handle_t, string) {
 }
 
 func getVerboseState() C.uint16_t {
-	if envconfig.Debug {
+	if envconfig.Debug() {
 		return C.uint16_t(1)
 	}
 	return C.uint16_t(0)
diff --git a/llm/memory_test.go b/llm/memory_test.go
index f972f9275..06ae74387 100644
--- a/llm/memory_test.go
+++ b/llm/memory_test.go
@@ -8,14 +8,14 @@ import (
 	"testing"
 
 	"github.com/ollama/ollama/api"
-	"github.com/ollama/ollama/envconfig"
 	"github.com/ollama/ollama/gpu"
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
 )
 
 func TestEstimateGPULayers(t *testing.T) {
-	envconfig.Debug = true
+	t.Setenv("OLLAMA_DEBUG", "1")
+
 	modelName := "dummy"
 	f, err := os.CreateTemp(t.TempDir(), modelName)
 	require.NoError(t, err)
diff --git a/llm/server.go b/llm/server.go
index 08463ef01..eb9666506 100644
--- a/llm/server.go
+++ b/llm/server.go
@@ -195,7 +195,7 @@ func NewLlamaServer(gpus gpu.GpuInfoList, model string, ggml *GGML, adapters, pr
 		params = append(params, "--n-gpu-layers", fmt.Sprintf("%d", opts.NumGPU))
 	}
 
-	if envconfig.Debug {
+	if envconfig.Debug() {
 		params = append(params, "--verbose")
 	}
 
@@ -381,7 +381,7 @@ func NewLlamaServer(gpus gpu.GpuInfoList, model string, ggml *GGML, adapters, pr
 		}
 
 		slog.Info("starting llama server", "cmd", s.cmd.String())
-		if envconfig.Debug {
+		if envconfig.Debug() {
 			filteredEnv := []string{}
 			for _, ev := range s.cmd.Env {
 				if strings.HasPrefix(ev, "CUDA_") ||
diff --git a/server/routes.go b/server/routes.go
index 0d7ca003c..c049421b7 100644
--- a/server/routes.go
+++ b/server/routes.go
@@ -1093,7 +1093,7 @@ func (s *Server) GenerateRoutes() http.Handler {
 
 func Serve(ln net.Listener) error {
 	level := slog.LevelInfo
-	if envconfig.Debug {
+	if envconfig.Debug() {
 		level = slog.LevelDebug
 	}
 

From 4f1afd575d1dfd803b0d9abb995862d61e8d0734 Mon Sep 17 00:00:00 2001
From: Michael Yang 
Date: Wed, 3 Jul 2024 16:44:57 -0700
Subject: [PATCH 125/384] host

---
 api/client.go            |   8 +--
 cmd/cmd.go               |   2 +-
 envconfig/config.go      | 107 ++++++++++++++++-----------------------
 envconfig/config_test.go |  62 +++++++++--------------
 4 files changed, 71 insertions(+), 108 deletions(-)

diff --git a/api/client.go b/api/client.go
index c59fbc423..e02b21bfa 100644
--- a/api/client.go
+++ b/api/client.go
@@ -20,7 +20,6 @@ import (
 	"encoding/json"
 	"fmt"
 	"io"
-	"net"
 	"net/http"
 	"net/url"
 	"runtime"
@@ -63,13 +62,8 @@ func checkError(resp *http.Response, body []byte) error {
 // If the variable is not specified, a default ollama host and port will be
 // used.
 func ClientFromEnvironment() (*Client, error) {
-	ollamaHost := envconfig.Host
-
 	return &Client{
-		base: &url.URL{
-			Scheme: ollamaHost.Scheme,
-			Host:   net.JoinHostPort(ollamaHost.Host, ollamaHost.Port),
-		},
+		base: envconfig.Host(),
 		http: http.DefaultClient,
 	}, nil
 }
diff --git a/cmd/cmd.go b/cmd/cmd.go
index b761d018f..5f3735f4f 100644
--- a/cmd/cmd.go
+++ b/cmd/cmd.go
@@ -1076,7 +1076,7 @@ func RunServer(cmd *cobra.Command, _ []string) error {
 		return err
 	}
 
-	ln, err := net.Listen("tcp", net.JoinHostPort(envconfig.Host.Host, envconfig.Host.Port))
+	ln, err := net.Listen("tcp", envconfig.Host().Host)
 	if err != nil {
 		return err
 	}
diff --git a/envconfig/config.go b/envconfig/config.go
index 426507be0..23f932706 100644
--- a/envconfig/config.go
+++ b/envconfig/config.go
@@ -6,6 +6,7 @@ import (
 	"log/slog"
 	"math"
 	"net"
+	"net/url"
 	"os"
 	"path/filepath"
 	"runtime"
@@ -14,16 +15,6 @@ import (
 	"time"
 )
 
-type OllamaHost struct {
-	Scheme string
-	Host   string
-	Port   string
-}
-
-func (o OllamaHost) String() string {
-	return fmt.Sprintf("%s://%s:%s", o.Scheme, o.Host, o.Port)
-}
-
 var ErrInvalidHostPort = errors.New("invalid port specified in OLLAMA_HOST")
 
 // Debug returns true if the OLLAMA_DEBUG environment variable is set to a truthy value.
@@ -41,13 +32,54 @@ func Debug() bool {
 	return false
 }
 
+// Host returns the scheme and host. Host can be configured via the OLLAMA_HOST environment variable.
+// Default is scheme "http" and host "127.0.0.1:11434"
+func Host() *url.URL {
+	defaultPort := "11434"
+
+	s := os.Getenv("OLLAMA_HOST")
+	s = strings.TrimSpace(strings.Trim(strings.TrimSpace(s), "\"'"))
+	scheme, hostport, ok := strings.Cut(s, "://")
+	switch {
+	case !ok:
+		scheme, hostport = "http", s
+	case scheme == "http":
+		defaultPort = "80"
+	case scheme == "https":
+		defaultPort = "443"
+	}
+
+	// trim trailing slashes
+	hostport = strings.TrimRight(hostport, "/")
+
+	host, port, err := net.SplitHostPort(hostport)
+	if err != nil {
+		host, port = "127.0.0.1", defaultPort
+		if ip := net.ParseIP(strings.Trim(hostport, "[]")); ip != nil {
+			host = ip.String()
+		} else if hostport != "" {
+			host = hostport
+		}
+	}
+
+	if n, err := strconv.ParseInt(port, 10, 32); err != nil || n > 65535 || n < 0 {
+		return &url.URL{
+			Scheme: scheme,
+			Host:   net.JoinHostPort(host, defaultPort),
+		}
+	}
+
+	return &url.URL{
+		Scheme: scheme,
+		Host:   net.JoinHostPort(host, port),
+	}
+}
+
 var (
 	// Set via OLLAMA_ORIGINS in the environment
 	AllowOrigins []string
 	// Experimental flash attention
 	FlashAttention bool
-	// Set via OLLAMA_HOST in the environment
-	Host *OllamaHost
 	// Set via OLLAMA_KEEP_ALIVE in the environment
 	KeepAlive time.Duration
 	// Set via OLLAMA_LLM_LIBRARY in the environment
@@ -95,7 +127,7 @@ func AsMap() map[string]EnvVar {
 	ret := map[string]EnvVar{
 		"OLLAMA_DEBUG":             {"OLLAMA_DEBUG", Debug(), "Show additional debug information (e.g. OLLAMA_DEBUG=1)"},
 		"OLLAMA_FLASH_ATTENTION":   {"OLLAMA_FLASH_ATTENTION", FlashAttention, "Enabled flash attention"},
-		"OLLAMA_HOST":              {"OLLAMA_HOST", Host, "IP Address for the ollama server (default 127.0.0.1:11434)"},
+		"OLLAMA_HOST":              {"OLLAMA_HOST", Host(), "IP Address for the ollama server (default 127.0.0.1:11434)"},
 		"OLLAMA_KEEP_ALIVE":        {"OLLAMA_KEEP_ALIVE", KeepAlive, "The duration that models stay loaded in memory (default \"5m\")"},
 		"OLLAMA_LLM_LIBRARY":       {"OLLAMA_LLM_LIBRARY", LLMLibrary, "Set LLM library to bypass autodetection"},
 		"OLLAMA_MAX_LOADED_MODELS": {"OLLAMA_MAX_LOADED_MODELS", MaxRunners, "Maximum number of loaded models per GPU"},
@@ -271,11 +303,6 @@ func LoadConfig() {
 		slog.Error("invalid setting", "OLLAMA_MODELS", ModelsDir, "error", err)
 	}
 
-	Host, err = getOllamaHost()
-	if err != nil {
-		slog.Error("invalid setting", "OLLAMA_HOST", Host, "error", err, "using default port", Host.Port)
-	}
-
 	if set, err := strconv.ParseBool(clean("OLLAMA_INTEL_GPU")); err == nil {
 		IntelGpu = set
 	}
@@ -298,50 +325,6 @@ func getModelsDir() (string, error) {
 	return filepath.Join(home, ".ollama", "models"), nil
 }
 
-func getOllamaHost() (*OllamaHost, error) {
-	defaultPort := "11434"
-
-	hostVar := os.Getenv("OLLAMA_HOST")
-	hostVar = strings.TrimSpace(strings.Trim(strings.TrimSpace(hostVar), "\"'"))
-
-	scheme, hostport, ok := strings.Cut(hostVar, "://")
-	switch {
-	case !ok:
-		scheme, hostport = "http", hostVar
-	case scheme == "http":
-		defaultPort = "80"
-	case scheme == "https":
-		defaultPort = "443"
-	}
-
-	// trim trailing slashes
-	hostport = strings.TrimRight(hostport, "/")
-
-	host, port, err := net.SplitHostPort(hostport)
-	if err != nil {
-		host, port = "127.0.0.1", defaultPort
-		if ip := net.ParseIP(strings.Trim(hostport, "[]")); ip != nil {
-			host = ip.String()
-		} else if hostport != "" {
-			host = hostport
-		}
-	}
-
-	if portNum, err := strconv.ParseInt(port, 10, 32); err != nil || portNum > 65535 || portNum < 0 {
-		return &OllamaHost{
-			Scheme: scheme,
-			Host:   host,
-			Port:   defaultPort,
-		}, ErrInvalidHostPort
-	}
-
-	return &OllamaHost{
-		Scheme: scheme,
-		Host:   host,
-		Port:   port,
-	}, nil
-}
-
 func loadKeepAlive(ka string) {
 	v, err := strconv.Atoi(ka)
 	if err != nil {
diff --git a/envconfig/config_test.go b/envconfig/config_test.go
index f083bb03c..af89e7b75 100644
--- a/envconfig/config_test.go
+++ b/envconfig/config_test.go
@@ -1,13 +1,10 @@
 package envconfig
 
 import (
-	"fmt"
 	"math"
-	"net"
 	"testing"
 	"time"
 
-	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
 )
 
@@ -42,45 +39,34 @@ func TestConfig(t *testing.T) {
 }
 
 func TestClientFromEnvironment(t *testing.T) {
-	type testCase struct {
+	cases := map[string]struct {
 		value  string
 		expect string
-		err    error
+	}{
+		"empty":               {"", "127.0.0.1:11434"},
+		"only address":        {"1.2.3.4", "1.2.3.4:11434"},
+		"only port":           {":1234", ":1234"},
+		"address and port":    {"1.2.3.4:1234", "1.2.3.4:1234"},
+		"hostname":            {"example.com", "example.com:11434"},
+		"hostname and port":   {"example.com:1234", "example.com:1234"},
+		"zero port":           {":0", ":0"},
+		"too large port":      {":66000", ":11434"},
+		"too small port":      {":-1", ":11434"},
+		"ipv6 localhost":      {"[::1]", "[::1]:11434"},
+		"ipv6 world open":     {"[::]", "[::]:11434"},
+		"ipv6 no brackets":    {"::1", "[::1]:11434"},
+		"ipv6 + port":         {"[::1]:1337", "[::1]:1337"},
+		"extra space":         {" 1.2.3.4 ", "1.2.3.4:11434"},
+		"extra quotes":        {"\"1.2.3.4\"", "1.2.3.4:11434"},
+		"extra space+quotes":  {" \" 1.2.3.4 \" ", "1.2.3.4:11434"},
+		"extra single quotes": {"'1.2.3.4'", "1.2.3.4:11434"},
 	}
 
-	hostTestCases := map[string]*testCase{
-		"empty":               {value: "", expect: "127.0.0.1:11434"},
-		"only address":        {value: "1.2.3.4", expect: "1.2.3.4:11434"},
-		"only port":           {value: ":1234", expect: ":1234"},
-		"address and port":    {value: "1.2.3.4:1234", expect: "1.2.3.4:1234"},
-		"hostname":            {value: "example.com", expect: "example.com:11434"},
-		"hostname and port":   {value: "example.com:1234", expect: "example.com:1234"},
-		"zero port":           {value: ":0", expect: ":0"},
-		"too large port":      {value: ":66000", err: ErrInvalidHostPort},
-		"too small port":      {value: ":-1", err: ErrInvalidHostPort},
-		"ipv6 localhost":      {value: "[::1]", expect: "[::1]:11434"},
-		"ipv6 world open":     {value: "[::]", expect: "[::]:11434"},
-		"ipv6 no brackets":    {value: "::1", expect: "[::1]:11434"},
-		"ipv6 + port":         {value: "[::1]:1337", expect: "[::1]:1337"},
-		"extra space":         {value: " 1.2.3.4 ", expect: "1.2.3.4:11434"},
-		"extra quotes":        {value: "\"1.2.3.4\"", expect: "1.2.3.4:11434"},
-		"extra space+quotes":  {value: " \" 1.2.3.4 \" ", expect: "1.2.3.4:11434"},
-		"extra single quotes": {value: "'1.2.3.4'", expect: "1.2.3.4:11434"},
-	}
-
-	for k, v := range hostTestCases {
-		t.Run(k, func(t *testing.T) {
-			t.Setenv("OLLAMA_HOST", v.value)
-			LoadConfig()
-
-			oh, err := getOllamaHost()
-			if err != v.err {
-				t.Fatalf("expected %s, got %s", v.err, err)
-			}
-
-			if err == nil {
-				host := net.JoinHostPort(oh.Host, oh.Port)
-				assert.Equal(t, v.expect, host, fmt.Sprintf("%s: expected %s, got %s", k, v.expect, host))
+	for name, tt := range cases {
+		t.Run(name, func(t *testing.T) {
+			t.Setenv("OLLAMA_HOST", tt.value)
+			if host := Host(); host.Host != tt.expect {
+				t.Errorf("%s: expected %s, got %s", name, tt.expect, host.Host)
 			}
 		})
 	}

From d1a5227cadf6ae736f8dd5cb9fb7452dd015f820 Mon Sep 17 00:00:00 2001
From: Michael Yang 
Date: Wed, 3 Jul 2024 17:02:07 -0700
Subject: [PATCH 126/384] origins

---
 envconfig/config.go      | 52 +++++++++++-----------
 envconfig/config_test.go | 95 +++++++++++++++++++++++++++++++++++++++-
 server/routes.go         |  2 +-
 3 files changed, 119 insertions(+), 30 deletions(-)

diff --git a/envconfig/config.go b/envconfig/config.go
index 23f932706..7ae521ab3 100644
--- a/envconfig/config.go
+++ b/envconfig/config.go
@@ -75,9 +75,31 @@ func Host() *url.URL {
 	}
 }
 
+// Origins returns a list of allowed origins. Origins can be configured via the OLLAMA_ORIGINS environment variable.
+func Origins() (origins []string) {
+	if s := clean("OLLAMA_ORIGINS"); s != "" {
+		origins = strings.Split(s, ",")
+	}
+
+	for _, origin := range []string{"localhost", "127.0.0.1", "0.0.0.0"} {
+		origins = append(origins,
+			fmt.Sprintf("http://%s", origin),
+			fmt.Sprintf("https://%s", origin),
+			fmt.Sprintf("http://%s", net.JoinHostPort(origin, "*")),
+			fmt.Sprintf("https://%s", net.JoinHostPort(origin, "*")),
+		)
+	}
+
+	origins = append(origins,
+		"app://*",
+		"file://*",
+		"tauri://*",
+	)
+
+	return origins
+}
+
 var (
-	// Set via OLLAMA_ORIGINS in the environment
-	AllowOrigins []string
 	// Experimental flash attention
 	FlashAttention bool
 	// Set via OLLAMA_KEEP_ALIVE in the environment
@@ -136,7 +158,7 @@ func AsMap() map[string]EnvVar {
 		"OLLAMA_NOHISTORY":         {"OLLAMA_NOHISTORY", NoHistory, "Do not preserve readline history"},
 		"OLLAMA_NOPRUNE":           {"OLLAMA_NOPRUNE", NoPrune, "Do not prune model blobs on startup"},
 		"OLLAMA_NUM_PARALLEL":      {"OLLAMA_NUM_PARALLEL", NumParallel, "Maximum number of parallel requests"},
-		"OLLAMA_ORIGINS":           {"OLLAMA_ORIGINS", AllowOrigins, "A comma separated list of allowed origins"},
+		"OLLAMA_ORIGINS":           {"OLLAMA_ORIGINS", Origins(), "A comma separated list of allowed origins"},
 		"OLLAMA_RUNNERS_DIR":       {"OLLAMA_RUNNERS_DIR", RunnersDir, "Location for runners"},
 		"OLLAMA_SCHED_SPREAD":      {"OLLAMA_SCHED_SPREAD", SchedSpread, "Always schedule model across all GPUs"},
 		"OLLAMA_TMPDIR":            {"OLLAMA_TMPDIR", TmpDir, "Location for temporary files"},
@@ -160,12 +182,6 @@ func Values() map[string]string {
 	return vals
 }
 
-var defaultAllowOrigins = []string{
-	"localhost",
-	"127.0.0.1",
-	"0.0.0.0",
-}
-
 // Clean quotes and spaces from the value
 func clean(key string) string {
 	return strings.Trim(os.Getenv(key), "\"' ")
@@ -255,24 +271,6 @@ func LoadConfig() {
 		NoPrune = true
 	}
 
-	if origins := clean("OLLAMA_ORIGINS"); origins != "" {
-		AllowOrigins = strings.Split(origins, ",")
-	}
-	for _, allowOrigin := range defaultAllowOrigins {
-		AllowOrigins = append(AllowOrigins,
-			fmt.Sprintf("http://%s", allowOrigin),
-			fmt.Sprintf("https://%s", allowOrigin),
-			fmt.Sprintf("http://%s", net.JoinHostPort(allowOrigin, "*")),
-			fmt.Sprintf("https://%s", net.JoinHostPort(allowOrigin, "*")),
-		)
-	}
-
-	AllowOrigins = append(AllowOrigins,
-		"app://*",
-		"file://*",
-		"tauri://*",
-	)
-
 	maxRunners := clean("OLLAMA_MAX_LOADED_MODELS")
 	if maxRunners != "" {
 		m, err := strconv.Atoi(maxRunners)
diff --git a/envconfig/config_test.go b/envconfig/config_test.go
index af89e7b75..dc65ef704 100644
--- a/envconfig/config_test.go
+++ b/envconfig/config_test.go
@@ -5,10 +5,11 @@ import (
 	"testing"
 	"time"
 
+	"github.com/google/go-cmp/cmp"
 	"github.com/stretchr/testify/require"
 )
 
-func TestConfig(t *testing.T) {
+func TestSmoke(t *testing.T) {
 	t.Setenv("OLLAMA_DEBUG", "")
 	require.False(t, Debug())
 
@@ -38,7 +39,7 @@ func TestConfig(t *testing.T) {
 	require.Equal(t, time.Duration(math.MaxInt64), KeepAlive)
 }
 
-func TestClientFromEnvironment(t *testing.T) {
+func TestHost(t *testing.T) {
 	cases := map[string]struct {
 		value  string
 		expect string
@@ -71,3 +72,93 @@ func TestClientFromEnvironment(t *testing.T) {
 		})
 	}
 }
+
+func TestOrigins(t *testing.T) {
+	cases := []struct {
+		value  string
+		expect []string
+	}{
+		{"", []string{
+			"http://localhost",
+			"https://localhost",
+			"http://localhost:*",
+			"https://localhost:*",
+			"http://127.0.0.1",
+			"https://127.0.0.1",
+			"http://127.0.0.1:*",
+			"https://127.0.0.1:*",
+			"http://0.0.0.0",
+			"https://0.0.0.0",
+			"http://0.0.0.0:*",
+			"https://0.0.0.0:*",
+			"app://*",
+			"file://*",
+			"tauri://*",
+		}},
+		{"http://10.0.0.1", []string{
+			"http://10.0.0.1",
+			"http://localhost",
+			"https://localhost",
+			"http://localhost:*",
+			"https://localhost:*",
+			"http://127.0.0.1",
+			"https://127.0.0.1",
+			"http://127.0.0.1:*",
+			"https://127.0.0.1:*",
+			"http://0.0.0.0",
+			"https://0.0.0.0",
+			"http://0.0.0.0:*",
+			"https://0.0.0.0:*",
+			"app://*",
+			"file://*",
+			"tauri://*",
+		}},
+		{"http://172.16.0.1,https://192.168.0.1", []string{
+			"http://172.16.0.1",
+			"https://192.168.0.1",
+			"http://localhost",
+			"https://localhost",
+			"http://localhost:*",
+			"https://localhost:*",
+			"http://127.0.0.1",
+			"https://127.0.0.1",
+			"http://127.0.0.1:*",
+			"https://127.0.0.1:*",
+			"http://0.0.0.0",
+			"https://0.0.0.0",
+			"http://0.0.0.0:*",
+			"https://0.0.0.0:*",
+			"app://*",
+			"file://*",
+			"tauri://*",
+		}},
+		{"http://totally.safe,http://definitely.legit", []string{
+			"http://totally.safe",
+			"http://definitely.legit",
+			"http://localhost",
+			"https://localhost",
+			"http://localhost:*",
+			"https://localhost:*",
+			"http://127.0.0.1",
+			"https://127.0.0.1",
+			"http://127.0.0.1:*",
+			"https://127.0.0.1:*",
+			"http://0.0.0.0",
+			"https://0.0.0.0",
+			"http://0.0.0.0:*",
+			"https://0.0.0.0:*",
+			"app://*",
+			"file://*",
+			"tauri://*",
+		}},
+	}
+	for _, tt := range cases {
+		t.Run(tt.value, func(t *testing.T) {
+			t.Setenv("OLLAMA_ORIGINS", tt.value)
+
+			if diff := cmp.Diff(Origins(), tt.expect); diff != "" {
+				t.Errorf("%s: mismatch (-want +got):\n%s", tt.value, diff)
+			}
+		})
+	}
+}
diff --git a/server/routes.go b/server/routes.go
index c049421b7..07898d9ba 100644
--- a/server/routes.go
+++ b/server/routes.go
@@ -1048,7 +1048,7 @@ func (s *Server) GenerateRoutes() http.Handler {
 	for _, prop := range openAIProperties {
 		config.AllowHeaders = append(config.AllowHeaders, "x-stainless-"+prop)
 	}
-	config.AllowOrigins = envconfig.AllowOrigins
+	config.AllowOrigins = envconfig.Origins()
 
 	r := gin.Default()
 	r.Use(

From 66fe77f0841622054e29f5fd3d3643f514991004 Mon Sep 17 00:00:00 2001
From: Michael Yang 
Date: Wed, 3 Jul 2024 17:07:42 -0700
Subject: [PATCH 127/384] models

---
 envconfig/config.go | 34 ++++++++++++++++------------------
 server/modelpath.go | 12 +++---------
 2 files changed, 19 insertions(+), 27 deletions(-)

diff --git a/envconfig/config.go b/envconfig/config.go
index 7ae521ab3..286f51d42 100644
--- a/envconfig/config.go
+++ b/envconfig/config.go
@@ -99,6 +99,21 @@ func Origins() (origins []string) {
 	return origins
 }
 
+// Models returns the path to the models directory. Models directory can be configured via the OLLAMA_MODELS environment variable.
+// Default is $HOME/.ollama/models
+func Models() string {
+	if s, ok := os.LookupEnv("OLLAMA_MODELS"); ok {
+		return s
+	}
+
+	home, err := os.UserHomeDir()
+	if err != nil {
+		panic(err)
+	}
+
+	return filepath.Join(home, ".ollama", "models")
+}
+
 var (
 	// Experimental flash attention
 	FlashAttention bool
@@ -154,7 +169,7 @@ func AsMap() map[string]EnvVar {
 		"OLLAMA_LLM_LIBRARY":       {"OLLAMA_LLM_LIBRARY", LLMLibrary, "Set LLM library to bypass autodetection"},
 		"OLLAMA_MAX_LOADED_MODELS": {"OLLAMA_MAX_LOADED_MODELS", MaxRunners, "Maximum number of loaded models per GPU"},
 		"OLLAMA_MAX_QUEUE":         {"OLLAMA_MAX_QUEUE", MaxQueuedRequests, "Maximum number of queued requests"},
-		"OLLAMA_MODELS":            {"OLLAMA_MODELS", ModelsDir, "The path to the models directory"},
+		"OLLAMA_MODELS":            {"OLLAMA_MODELS", Models(), "The path to the models directory"},
 		"OLLAMA_NOHISTORY":         {"OLLAMA_NOHISTORY", NoHistory, "Do not preserve readline history"},
 		"OLLAMA_NOPRUNE":           {"OLLAMA_NOPRUNE", NoPrune, "Do not prune model blobs on startup"},
 		"OLLAMA_NUM_PARALLEL":      {"OLLAMA_NUM_PARALLEL", NumParallel, "Maximum number of parallel requests"},
@@ -295,12 +310,6 @@ func LoadConfig() {
 		loadKeepAlive(ka)
 	}
 
-	var err error
-	ModelsDir, err = getModelsDir()
-	if err != nil {
-		slog.Error("invalid setting", "OLLAMA_MODELS", ModelsDir, "error", err)
-	}
-
 	if set, err := strconv.ParseBool(clean("OLLAMA_INTEL_GPU")); err == nil {
 		IntelGpu = set
 	}
@@ -312,17 +321,6 @@ func LoadConfig() {
 	HsaOverrideGfxVersion = clean("HSA_OVERRIDE_GFX_VERSION")
 }
 
-func getModelsDir() (string, error) {
-	if models, exists := os.LookupEnv("OLLAMA_MODELS"); exists {
-		return models, nil
-	}
-	home, err := os.UserHomeDir()
-	if err != nil {
-		return "", err
-	}
-	return filepath.Join(home, ".ollama", "models"), nil
-}
-
 func loadKeepAlive(ka string) {
 	v, err := strconv.Atoi(ka)
 	if err != nil {
diff --git a/server/modelpath.go b/server/modelpath.go
index 3fdb4238f..354eeed78 100644
--- a/server/modelpath.go
+++ b/server/modelpath.go
@@ -105,9 +105,7 @@ func (mp ModelPath) GetShortTagname() string {
 
 // GetManifestPath returns the path to the manifest file for the given model path, it is up to the caller to create the directory if it does not exist.
 func (mp ModelPath) GetManifestPath() (string, error) {
-	dir := envconfig.ModelsDir
-
-	return filepath.Join(dir, "manifests", mp.Registry, mp.Namespace, mp.Repository, mp.Tag), nil
+	return filepath.Join(envconfig.Models(), "manifests", mp.Registry, mp.Namespace, mp.Repository, mp.Tag), nil
 }
 
 func (mp ModelPath) BaseURL() *url.URL {
@@ -118,9 +116,7 @@ func (mp ModelPath) BaseURL() *url.URL {
 }
 
 func GetManifestPath() (string, error) {
-	dir := envconfig.ModelsDir
-
-	path := filepath.Join(dir, "manifests")
+	path := filepath.Join(envconfig.Models(), "manifests")
 	if err := os.MkdirAll(path, 0o755); err != nil {
 		return "", err
 	}
@@ -129,8 +125,6 @@ func GetManifestPath() (string, error) {
 }
 
 func GetBlobsPath(digest string) (string, error) {
-	dir := envconfig.ModelsDir
-
 	// only accept actual sha256 digests
 	pattern := "^sha256[:-][0-9a-fA-F]{64}$"
 	re := regexp.MustCompile(pattern)
@@ -140,7 +134,7 @@ func GetBlobsPath(digest string) (string, error) {
 	}
 
 	digest = strings.ReplaceAll(digest, ":", "-")
-	path := filepath.Join(dir, "blobs", digest)
+	path := filepath.Join(envconfig.Models(), "blobs", digest)
 	dirPath := filepath.Dir(path)
 	if digest == "" {
 		dirPath = path

From 55cd3ddccac14d48f5f129ec35b3a109be215d01 Mon Sep 17 00:00:00 2001
From: Michael Yang 
Date: Wed, 3 Jul 2024 17:22:13 -0700
Subject: [PATCH 128/384] bool

---
 cmd/interactive.go       |   2 +-
 envconfig/config.go      | 123 ++++++++++++++++-----------------------
 envconfig/config_test.go |  28 ++++++++-
 gpu/gpu.go               |   2 +-
 llm/server.go            |   2 +-
 server/images.go         |   4 +-
 server/routes.go         |   2 +-
 server/sched.go          |   2 +-
 8 files changed, 82 insertions(+), 83 deletions(-)

diff --git a/cmd/interactive.go b/cmd/interactive.go
index adbc3e9fb..9fb668513 100644
--- a/cmd/interactive.go
+++ b/cmd/interactive.go
@@ -157,7 +157,7 @@ func generateInteractive(cmd *cobra.Command, opts runOptions) error {
 		return err
 	}
 
-	if envconfig.NoHistory {
+	if envconfig.NoHistory() {
 		scanner.HistoryDisable()
 	}
 
diff --git a/envconfig/config.go b/envconfig/config.go
index 286f51d42..ea78585bb 100644
--- a/envconfig/config.go
+++ b/envconfig/config.go
@@ -17,21 +17,6 @@ import (
 
 var ErrInvalidHostPort = errors.New("invalid port specified in OLLAMA_HOST")
 
-// Debug returns true if the OLLAMA_DEBUG environment variable is set to a truthy value.
-func Debug() bool {
-	if s := clean("OLLAMA_DEBUG"); s != "" {
-		b, err := strconv.ParseBool(s)
-		if err != nil {
-			// non-empty value is truthy
-			return true
-		}
-
-		return b
-	}
-
-	return false
-}
-
 // Host returns the scheme and host. Host can be configured via the OLLAMA_HOST environment variable.
 // Default is scheme "http" and host "127.0.0.1:11434"
 func Host() *url.URL {
@@ -77,7 +62,7 @@ func Host() *url.URL {
 
 // Origins returns a list of allowed origins. Origins can be configured via the OLLAMA_ORIGINS environment variable.
 func Origins() (origins []string) {
-	if s := clean("OLLAMA_ORIGINS"); s != "" {
+	if s := getenv("OLLAMA_ORIGINS"); s != "" {
 		origins = strings.Split(s, ",")
 	}
 
@@ -114,9 +99,37 @@ func Models() string {
 	return filepath.Join(home, ".ollama", "models")
 }
 
+func Bool(k string) func() bool {
+	return func() bool {
+		if s := getenv(k); s != "" {
+			b, err := strconv.ParseBool(s)
+			if err != nil {
+				return true
+			}
+
+			return b
+		}
+
+		return false
+	}
+}
+
+var (
+	// Debug enabled additional debug information.
+	Debug = Bool("OLLAMA_DEBUG")
+	// FlashAttention enables the experimental flash attention feature.
+	FlashAttention = Bool("OLLAMA_FLASH_ATTENTION")
+	// NoHistory disables readline history.
+	NoHistory = Bool("OLLAMA_NOHISTORY")
+	// NoPrune disables pruning of model blobs on startup.
+	NoPrune = Bool("OLLAMA_NOPRUNE")
+	// SchedSpread allows scheduling models across all GPUs.
+	SchedSpread = Bool("OLLAMA_SCHED_SPREAD")
+	// IntelGPU enables experimental Intel GPU detection.
+	IntelGPU = Bool("OLLAMA_INTEL_GPU")
+)
+
 var (
-	// Experimental flash attention
-	FlashAttention bool
 	// Set via OLLAMA_KEEP_ALIVE in the environment
 	KeepAlive time.Duration
 	// Set via OLLAMA_LLM_LIBRARY in the environment
@@ -125,22 +138,12 @@ var (
 	MaxRunners int
 	// Set via OLLAMA_MAX_QUEUE in the environment
 	MaxQueuedRequests int
-	// Set via OLLAMA_MODELS in the environment
-	ModelsDir string
-	// Set via OLLAMA_NOHISTORY in the environment
-	NoHistory bool
-	// Set via OLLAMA_NOPRUNE in the environment
-	NoPrune bool
 	// Set via OLLAMA_NUM_PARALLEL in the environment
 	NumParallel int
 	// Set via OLLAMA_RUNNERS_DIR in the environment
 	RunnersDir string
-	// Set via OLLAMA_SCHED_SPREAD in the environment
-	SchedSpread bool
 	// Set via OLLAMA_TMPDIR in the environment
 	TmpDir string
-	// Set via OLLAMA_INTEL_GPU in the environment
-	IntelGpu bool
 
 	// Set via CUDA_VISIBLE_DEVICES in the environment
 	CudaVisibleDevices string
@@ -163,19 +166,19 @@ type EnvVar struct {
 func AsMap() map[string]EnvVar {
 	ret := map[string]EnvVar{
 		"OLLAMA_DEBUG":             {"OLLAMA_DEBUG", Debug(), "Show additional debug information (e.g. OLLAMA_DEBUG=1)"},
-		"OLLAMA_FLASH_ATTENTION":   {"OLLAMA_FLASH_ATTENTION", FlashAttention, "Enabled flash attention"},
+		"OLLAMA_FLASH_ATTENTION":   {"OLLAMA_FLASH_ATTENTION", FlashAttention(), "Enabled flash attention"},
 		"OLLAMA_HOST":              {"OLLAMA_HOST", Host(), "IP Address for the ollama server (default 127.0.0.1:11434)"},
 		"OLLAMA_KEEP_ALIVE":        {"OLLAMA_KEEP_ALIVE", KeepAlive, "The duration that models stay loaded in memory (default \"5m\")"},
 		"OLLAMA_LLM_LIBRARY":       {"OLLAMA_LLM_LIBRARY", LLMLibrary, "Set LLM library to bypass autodetection"},
 		"OLLAMA_MAX_LOADED_MODELS": {"OLLAMA_MAX_LOADED_MODELS", MaxRunners, "Maximum number of loaded models per GPU"},
 		"OLLAMA_MAX_QUEUE":         {"OLLAMA_MAX_QUEUE", MaxQueuedRequests, "Maximum number of queued requests"},
 		"OLLAMA_MODELS":            {"OLLAMA_MODELS", Models(), "The path to the models directory"},
-		"OLLAMA_NOHISTORY":         {"OLLAMA_NOHISTORY", NoHistory, "Do not preserve readline history"},
-		"OLLAMA_NOPRUNE":           {"OLLAMA_NOPRUNE", NoPrune, "Do not prune model blobs on startup"},
+		"OLLAMA_NOHISTORY":         {"OLLAMA_NOHISTORY", NoHistory(), "Do not preserve readline history"},
+		"OLLAMA_NOPRUNE":           {"OLLAMA_NOPRUNE", NoPrune(), "Do not prune model blobs on startup"},
 		"OLLAMA_NUM_PARALLEL":      {"OLLAMA_NUM_PARALLEL", NumParallel, "Maximum number of parallel requests"},
 		"OLLAMA_ORIGINS":           {"OLLAMA_ORIGINS", Origins(), "A comma separated list of allowed origins"},
 		"OLLAMA_RUNNERS_DIR":       {"OLLAMA_RUNNERS_DIR", RunnersDir, "Location for runners"},
-		"OLLAMA_SCHED_SPREAD":      {"OLLAMA_SCHED_SPREAD", SchedSpread, "Always schedule model across all GPUs"},
+		"OLLAMA_SCHED_SPREAD":      {"OLLAMA_SCHED_SPREAD", SchedSpread(), "Always schedule model across all GPUs"},
 		"OLLAMA_TMPDIR":            {"OLLAMA_TMPDIR", TmpDir, "Location for temporary files"},
 	}
 	if runtime.GOOS != "darwin" {
@@ -184,7 +187,7 @@ func AsMap() map[string]EnvVar {
 		ret["ROCR_VISIBLE_DEVICES"] = EnvVar{"ROCR_VISIBLE_DEVICES", RocrVisibleDevices, "Set which AMD devices are visible"}
 		ret["GPU_DEVICE_ORDINAL"] = EnvVar{"GPU_DEVICE_ORDINAL", GpuDeviceOrdinal, "Set which AMD devices are visible"}
 		ret["HSA_OVERRIDE_GFX_VERSION"] = EnvVar{"HSA_OVERRIDE_GFX_VERSION", HsaOverrideGfxVersion, "Override the gfx used for all detected AMD GPUs"}
-		ret["OLLAMA_INTEL_GPU"] = EnvVar{"OLLAMA_INTEL_GPU", IntelGpu, "Enable experimental Intel GPU detection"}
+		ret["OLLAMA_INTEL_GPU"] = EnvVar{"OLLAMA_INTEL_GPU", IntelGPU(), "Enable experimental Intel GPU detection"}
 	}
 	return ret
 }
@@ -197,8 +200,8 @@ func Values() map[string]string {
 	return vals
 }
 
-// Clean quotes and spaces from the value
-func clean(key string) string {
+// getenv returns an environment variable stripped of leading and trailing quotes or spaces
+func getenv(key string) string {
 	return strings.Trim(os.Getenv(key), "\"' ")
 }
 
@@ -213,14 +216,7 @@ func init() {
 }
 
 func LoadConfig() {
-	if fa := clean("OLLAMA_FLASH_ATTENTION"); fa != "" {
-		d, err := strconv.ParseBool(fa)
-		if err == nil {
-			FlashAttention = d
-		}
-	}
-
-	RunnersDir = clean("OLLAMA_RUNNERS_DIR")
+	RunnersDir = getenv("OLLAMA_RUNNERS_DIR")
 	if runtime.GOOS == "windows" && RunnersDir == "" {
 		// On Windows we do not carry the payloads inside the main executable
 		appExe, err := os.Executable()
@@ -256,11 +252,11 @@ func LoadConfig() {
 		}
 	}
 
-	TmpDir = clean("OLLAMA_TMPDIR")
+	TmpDir = getenv("OLLAMA_TMPDIR")
 
-	LLMLibrary = clean("OLLAMA_LLM_LIBRARY")
+	LLMLibrary = getenv("OLLAMA_LLM_LIBRARY")
 
-	if onp := clean("OLLAMA_NUM_PARALLEL"); onp != "" {
+	if onp := getenv("OLLAMA_NUM_PARALLEL"); onp != "" {
 		val, err := strconv.Atoi(onp)
 		if err != nil {
 			slog.Error("invalid setting, ignoring", "OLLAMA_NUM_PARALLEL", onp, "error", err)
@@ -269,24 +265,7 @@ func LoadConfig() {
 		}
 	}
 
-	if nohistory := clean("OLLAMA_NOHISTORY"); nohistory != "" {
-		NoHistory = true
-	}
-
-	if spread := clean("OLLAMA_SCHED_SPREAD"); spread != "" {
-		s, err := strconv.ParseBool(spread)
-		if err == nil {
-			SchedSpread = s
-		} else {
-			SchedSpread = true
-		}
-	}
-
-	if noprune := clean("OLLAMA_NOPRUNE"); noprune != "" {
-		NoPrune = true
-	}
-
-	maxRunners := clean("OLLAMA_MAX_LOADED_MODELS")
+	maxRunners := getenv("OLLAMA_MAX_LOADED_MODELS")
 	if maxRunners != "" {
 		m, err := strconv.Atoi(maxRunners)
 		if err != nil {
@@ -305,20 +284,16 @@ func LoadConfig() {
 		}
 	}
 
-	ka := clean("OLLAMA_KEEP_ALIVE")
+	ka := getenv("OLLAMA_KEEP_ALIVE")
 	if ka != "" {
 		loadKeepAlive(ka)
 	}
 
-	if set, err := strconv.ParseBool(clean("OLLAMA_INTEL_GPU")); err == nil {
-		IntelGpu = set
-	}
-
-	CudaVisibleDevices = clean("CUDA_VISIBLE_DEVICES")
-	HipVisibleDevices = clean("HIP_VISIBLE_DEVICES")
-	RocrVisibleDevices = clean("ROCR_VISIBLE_DEVICES")
-	GpuDeviceOrdinal = clean("GPU_DEVICE_ORDINAL")
-	HsaOverrideGfxVersion = clean("HSA_OVERRIDE_GFX_VERSION")
+	CudaVisibleDevices = getenv("CUDA_VISIBLE_DEVICES")
+	HipVisibleDevices = getenv("HIP_VISIBLE_DEVICES")
+	RocrVisibleDevices = getenv("ROCR_VISIBLE_DEVICES")
+	GpuDeviceOrdinal = getenv("GPU_DEVICE_ORDINAL")
+	HsaOverrideGfxVersion = getenv("HSA_OVERRIDE_GFX_VERSION")
 }
 
 func loadKeepAlive(ka string) {
diff --git a/envconfig/config_test.go b/envconfig/config_test.go
index dc65ef704..b364b0094 100644
--- a/envconfig/config_test.go
+++ b/envconfig/config_test.go
@@ -20,8 +20,8 @@ func TestSmoke(t *testing.T) {
 	require.True(t, Debug())
 
 	t.Setenv("OLLAMA_FLASH_ATTENTION", "1")
-	LoadConfig()
-	require.True(t, FlashAttention)
+	require.True(t, FlashAttention())
+
 	t.Setenv("OLLAMA_KEEP_ALIVE", "")
 	LoadConfig()
 	require.Equal(t, 5*time.Minute, KeepAlive)
@@ -162,3 +162,27 @@ func TestOrigins(t *testing.T) {
 		})
 	}
 }
+
+func TestBool(t *testing.T) {
+	cases := map[string]struct {
+		value  string
+		expect bool
+	}{
+		"empty":     {"", false},
+		"true":      {"true", true},
+		"false":     {"false", false},
+		"1":         {"1", true},
+		"0":         {"0", false},
+		"random":    {"random", true},
+		"something": {"something", true},
+	}
+
+	for name, tt := range cases {
+		t.Run(name, func(t *testing.T) {
+			t.Setenv("OLLAMA_BOOL", tt.value)
+			if b := Bool("OLLAMA_BOOL"); b() != tt.expect {
+				t.Errorf("%s: expected %t, got %t", name, tt.expect, b())
+			}
+		})
+	}
+}
diff --git a/gpu/gpu.go b/gpu/gpu.go
index 1815668fd..c30595427 100644
--- a/gpu/gpu.go
+++ b/gpu/gpu.go
@@ -302,7 +302,7 @@ func GetGPUInfo() GpuInfoList {
 		}
 
 		// Intel
-		if envconfig.IntelGpu {
+		if envconfig.IntelGPU() {
 			oHandles = initOneAPIHandles()
 			// On windows we bundle the oneapi library one level above the runner dir
 			depPath = ""
diff --git a/llm/server.go b/llm/server.go
index eb9666506..84d9e93a4 100644
--- a/llm/server.go
+++ b/llm/server.go
@@ -221,7 +221,7 @@ func NewLlamaServer(gpus gpu.GpuInfoList, model string, ggml *GGML, adapters, pr
 		params = append(params, "--memory-f32")
 	}
 
-	flashAttnEnabled := envconfig.FlashAttention
+	flashAttnEnabled := envconfig.FlashAttention()
 
 	for _, g := range gpus {
 		// only cuda (compute capability 7+) and metal support flash attention
diff --git a/server/images.go b/server/images.go
index 574dec191..3eb3b3faa 100644
--- a/server/images.go
+++ b/server/images.go
@@ -644,7 +644,7 @@ func CreateModel(ctx context.Context, name model.Name, modelFileDir, quantizatio
 		return err
 	}
 
-	if !envconfig.NoPrune && old != nil {
+	if !envconfig.NoPrune() && old != nil {
 		if err := old.RemoveLayers(); err != nil {
 			return err
 		}
@@ -883,7 +883,7 @@ func PullModel(ctx context.Context, name string, regOpts *registryOptions, fn fu
 	// build deleteMap to prune unused layers
 	deleteMap := make(map[string]struct{})
 
-	if !envconfig.NoPrune {
+	if !envconfig.NoPrune() {
 		manifest, _, err = GetManifest(mp)
 		if err != nil && !errors.Is(err, os.ErrNotExist) {
 			return err
diff --git a/server/routes.go b/server/routes.go
index 07898d9ba..41a73cb46 100644
--- a/server/routes.go
+++ b/server/routes.go
@@ -1121,7 +1121,7 @@ func Serve(ln net.Listener) error {
 		return err
 	}
 
-	if !envconfig.NoPrune {
+	if !envconfig.NoPrune() {
 		// clean up unused layers and manifests
 		if err := PruneLayers(); err != nil {
 			return err
diff --git a/server/sched.go b/server/sched.go
index 2daed3abb..e1e986a5f 100644
--- a/server/sched.go
+++ b/server/sched.go
@@ -695,7 +695,7 @@ func pickBestFitGPUs(req *LlmRequest, ggml *llm.GGML, gpus gpu.GpuInfoList, numP
 		// First attempt to fit the model into a single GPU
 		for _, p := range numParallelToTry {
 			req.opts.NumCtx = req.origNumCtx * p
-			if !envconfig.SchedSpread {
+			if !envconfig.SchedSpread() {
 				for _, g := range sgl {
 					if ok, estimatedVRAM = llm.PredictServerFit([]gpu.GpuInfo{g}, ggml, req.model.AdapterPaths, req.model.ProjectorPaths, req.opts); ok {
 						slog.Info("new model will fit in available VRAM in single GPU, loading", "model", req.model.ModelPath, "gpu", g.ID, "parallel", p, "available", g.FreeMemory, "required", format.HumanBytes2(estimatedVRAM))

From 8570c1c0ef73e89448f6724645f56b9b10efef44 Mon Sep 17 00:00:00 2001
From: Michael Yang 
Date: Wed, 3 Jul 2024 18:39:35 -0700
Subject: [PATCH 129/384] keepalive

---
 envconfig/config.go      | 51 +++++++++++++++++-----------------------
 envconfig/config_test.go | 49 +++++++++++++++++++++++++-------------
 server/sched.go          |  2 +-
 3 files changed, 55 insertions(+), 47 deletions(-)

diff --git a/envconfig/config.go b/envconfig/config.go
index ea78585bb..62bfad648 100644
--- a/envconfig/config.go
+++ b/envconfig/config.go
@@ -99,6 +99,26 @@ func Models() string {
 	return filepath.Join(home, ".ollama", "models")
 }
 
+// KeepAlive returns the duration that models stay loaded in memory. KeepAlive can be configured via the OLLAMA_KEEP_ALIVE environment variable.
+// Negative values are treated as infinite. Zero is treated as no keep alive.
+// Default is 5 minutes.
+func KeepAlive() (keepAlive time.Duration) {
+	keepAlive = 5 * time.Minute
+	if s := os.Getenv("OLLAMA_KEEP_ALIVE"); s != "" {
+		if d, err := time.ParseDuration(s); err == nil {
+			keepAlive = d
+		} else if n, err := strconv.ParseInt(s, 10, 64); err == nil {
+			keepAlive = time.Duration(n) * time.Second
+		}
+	}
+
+	if keepAlive < 0 {
+		return time.Duration(math.MaxInt64)
+	}
+
+	return keepAlive
+}
+
 func Bool(k string) func() bool {
 	return func() bool {
 		if s := getenv(k); s != "" {
@@ -130,8 +150,6 @@ var (
 )
 
 var (
-	// Set via OLLAMA_KEEP_ALIVE in the environment
-	KeepAlive time.Duration
 	// Set via OLLAMA_LLM_LIBRARY in the environment
 	LLMLibrary string
 	// Set via OLLAMA_MAX_LOADED_MODELS in the environment
@@ -168,7 +186,7 @@ func AsMap() map[string]EnvVar {
 		"OLLAMA_DEBUG":             {"OLLAMA_DEBUG", Debug(), "Show additional debug information (e.g. OLLAMA_DEBUG=1)"},
 		"OLLAMA_FLASH_ATTENTION":   {"OLLAMA_FLASH_ATTENTION", FlashAttention(), "Enabled flash attention"},
 		"OLLAMA_HOST":              {"OLLAMA_HOST", Host(), "IP Address for the ollama server (default 127.0.0.1:11434)"},
-		"OLLAMA_KEEP_ALIVE":        {"OLLAMA_KEEP_ALIVE", KeepAlive, "The duration that models stay loaded in memory (default \"5m\")"},
+		"OLLAMA_KEEP_ALIVE":        {"OLLAMA_KEEP_ALIVE", KeepAlive(), "The duration that models stay loaded in memory (default \"5m\")"},
 		"OLLAMA_LLM_LIBRARY":       {"OLLAMA_LLM_LIBRARY", LLMLibrary, "Set LLM library to bypass autodetection"},
 		"OLLAMA_MAX_LOADED_MODELS": {"OLLAMA_MAX_LOADED_MODELS", MaxRunners, "Maximum number of loaded models per GPU"},
 		"OLLAMA_MAX_QUEUE":         {"OLLAMA_MAX_QUEUE", MaxQueuedRequests, "Maximum number of queued requests"},
@@ -210,7 +228,6 @@ func init() {
 	NumParallel = 0 // Autoselect
 	MaxRunners = 0  // Autoselect
 	MaxQueuedRequests = 512
-	KeepAlive = 5 * time.Minute
 
 	LoadConfig()
 }
@@ -284,35 +301,9 @@ func LoadConfig() {
 		}
 	}
 
-	ka := getenv("OLLAMA_KEEP_ALIVE")
-	if ka != "" {
-		loadKeepAlive(ka)
-	}
-
 	CudaVisibleDevices = getenv("CUDA_VISIBLE_DEVICES")
 	HipVisibleDevices = getenv("HIP_VISIBLE_DEVICES")
 	RocrVisibleDevices = getenv("ROCR_VISIBLE_DEVICES")
 	GpuDeviceOrdinal = getenv("GPU_DEVICE_ORDINAL")
 	HsaOverrideGfxVersion = getenv("HSA_OVERRIDE_GFX_VERSION")
 }
-
-func loadKeepAlive(ka string) {
-	v, err := strconv.Atoi(ka)
-	if err != nil {
-		d, err := time.ParseDuration(ka)
-		if err == nil {
-			if d < 0 {
-				KeepAlive = time.Duration(math.MaxInt64)
-			} else {
-				KeepAlive = d
-			}
-		}
-	} else {
-		d := time.Duration(v) * time.Second
-		if d < 0 {
-			KeepAlive = time.Duration(math.MaxInt64)
-		} else {
-			KeepAlive = d
-		}
-	}
-}
diff --git a/envconfig/config_test.go b/envconfig/config_test.go
index b364b0094..87c808ca9 100644
--- a/envconfig/config_test.go
+++ b/envconfig/config_test.go
@@ -21,22 +21,6 @@ func TestSmoke(t *testing.T) {
 
 	t.Setenv("OLLAMA_FLASH_ATTENTION", "1")
 	require.True(t, FlashAttention())
-
-	t.Setenv("OLLAMA_KEEP_ALIVE", "")
-	LoadConfig()
-	require.Equal(t, 5*time.Minute, KeepAlive)
-	t.Setenv("OLLAMA_KEEP_ALIVE", "3")
-	LoadConfig()
-	require.Equal(t, 3*time.Second, KeepAlive)
-	t.Setenv("OLLAMA_KEEP_ALIVE", "1h")
-	LoadConfig()
-	require.Equal(t, 1*time.Hour, KeepAlive)
-	t.Setenv("OLLAMA_KEEP_ALIVE", "-1s")
-	LoadConfig()
-	require.Equal(t, time.Duration(math.MaxInt64), KeepAlive)
-	t.Setenv("OLLAMA_KEEP_ALIVE", "-1")
-	LoadConfig()
-	require.Equal(t, time.Duration(math.MaxInt64), KeepAlive)
 }
 
 func TestHost(t *testing.T) {
@@ -186,3 +170,36 @@ func TestBool(t *testing.T) {
 		})
 	}
 }
+
+func TestKeepAlive(t *testing.T) {
+	cases := map[string]time.Duration{
+		"":       5 * time.Minute,
+		"1s":     time.Second,
+		"1m":     time.Minute,
+		"1h":     time.Hour,
+		"5m0s":   5 * time.Minute,
+		"1h2m3s": 1*time.Hour + 2*time.Minute + 3*time.Second,
+		"0":      time.Duration(0),
+		"60":     60 * time.Second,
+		"120":    2 * time.Minute,
+		"3600":   time.Hour,
+		"-0":     time.Duration(0),
+		"-1":     time.Duration(math.MaxInt64),
+		"-1m":    time.Duration(math.MaxInt64),
+		// invalid values
+		" ":   5 * time.Minute,
+		"???": 5 * time.Minute,
+		"1d":  5 * time.Minute,
+		"1y":  5 * time.Minute,
+		"1w":  5 * time.Minute,
+	}
+
+	for tt, expect := range cases {
+		t.Run(tt, func(t *testing.T) {
+			t.Setenv("OLLAMA_KEEP_ALIVE", tt)
+			if actual := KeepAlive(); actual != expect {
+				t.Errorf("%s: expected %s, got %s", tt, expect, actual)
+			}
+		})
+	}
+}
diff --git a/server/sched.go b/server/sched.go
index e1e986a5f..ad40c4ef0 100644
--- a/server/sched.go
+++ b/server/sched.go
@@ -401,7 +401,7 @@ func (s *Scheduler) load(req *LlmRequest, ggml *llm.GGML, gpus gpu.GpuInfoList,
 	if numParallel < 1 {
 		numParallel = 1
 	}
-	sessionDuration := envconfig.KeepAlive
+	sessionDuration := envconfig.KeepAlive()
 	if req.sessionDuration != nil {
 		sessionDuration = req.sessionDuration.Duration
 	}

From e2c3f6b3e2de014656ab9ddffccf7b89d1bcc09e Mon Sep 17 00:00:00 2001
From: Michael Yang 
Date: Wed, 3 Jul 2024 19:30:19 -0700
Subject: [PATCH 130/384] string

---
 envconfig/config.go | 143 ++++++++++++++++++++++----------------------
 gpu/amd_linux.go    |   8 +--
 gpu/amd_windows.go  |   2 +-
 gpu/assets.go       |   6 +-
 gpu/gpu.go          |   8 +--
 llm/server.go       |   2 +-
 6 files changed, 85 insertions(+), 84 deletions(-)

diff --git a/envconfig/config.go b/envconfig/config.go
index 62bfad648..34cc4dac5 100644
--- a/envconfig/config.go
+++ b/envconfig/config.go
@@ -149,30 +149,77 @@ var (
 	IntelGPU = Bool("OLLAMA_INTEL_GPU")
 )
 
+func String(s string) func() string {
+	return func() string {
+		return getenv(s)
+	}
+}
+
+var (
+	LLMLibrary = String("OLLAMA_LLM_LIBRARY")
+	TmpDir     = String("OLLAMA_TMPDIR")
+
+	CudaVisibleDevices    = String("CUDA_VISIBLE_DEVICES")
+	HipVisibleDevices     = String("HIP_VISIBLE_DEVICES")
+	RocrVisibleDevices    = String("ROCR_VISIBLE_DEVICES")
+	GpuDeviceOrdinal      = String("GPU_DEVICE_ORDINAL")
+	HsaOverrideGfxVersion = String("HSA_OVERRIDE_GFX_VERSION")
+)
+
+func RunnersDir() (p string) {
+	if p := getenv("OLLAMA_RUNNERS_DIR"); p != "" {
+		return p
+	}
+
+	if runtime.GOOS != "windows" {
+		return
+	}
+
+	defer func() {
+		if p == "" {
+			slog.Error("unable to locate llm runner directory. Set OLLAMA_RUNNERS_DIR to the location of 'ollama_runners'")
+		}
+	}()
+
+	// On Windows we do not carry the payloads inside the main executable
+	exe, err := os.Executable()
+	if err != nil {
+		return
+	}
+
+	cwd, err := os.Getwd()
+	if err != nil {
+		return
+	}
+
+	var paths []string
+	for _, root := range []string{filepath.Dir(exe), cwd} {
+		paths = append(paths,
+			root,
+			filepath.Join(root, "windows-"+runtime.GOARCH),
+			filepath.Join(root, "dist", "windows-"+runtime.GOARCH),
+		)
+	}
+
+	// Try a few variations to improve developer experience when building from source in the local tree
+	for _, path := range paths {
+		candidate := filepath.Join(path, "ollama_runners")
+		if _, err := os.Stat(candidate); err == nil {
+			p = candidate
+			break
+		}
+	}
+
+	return p
+}
+
 var (
-	// Set via OLLAMA_LLM_LIBRARY in the environment
-	LLMLibrary string
 	// Set via OLLAMA_MAX_LOADED_MODELS in the environment
 	MaxRunners int
 	// Set via OLLAMA_MAX_QUEUE in the environment
 	MaxQueuedRequests int
 	// Set via OLLAMA_NUM_PARALLEL in the environment
 	NumParallel int
-	// Set via OLLAMA_RUNNERS_DIR in the environment
-	RunnersDir string
-	// Set via OLLAMA_TMPDIR in the environment
-	TmpDir string
-
-	// Set via CUDA_VISIBLE_DEVICES in the environment
-	CudaVisibleDevices string
-	// Set via HIP_VISIBLE_DEVICES in the environment
-	HipVisibleDevices string
-	// Set via ROCR_VISIBLE_DEVICES in the environment
-	RocrVisibleDevices string
-	// Set via GPU_DEVICE_ORDINAL in the environment
-	GpuDeviceOrdinal string
-	// Set via HSA_OVERRIDE_GFX_VERSION in the environment
-	HsaOverrideGfxVersion string
 )
 
 type EnvVar struct {
@@ -187,7 +234,7 @@ func AsMap() map[string]EnvVar {
 		"OLLAMA_FLASH_ATTENTION":   {"OLLAMA_FLASH_ATTENTION", FlashAttention(), "Enabled flash attention"},
 		"OLLAMA_HOST":              {"OLLAMA_HOST", Host(), "IP Address for the ollama server (default 127.0.0.1:11434)"},
 		"OLLAMA_KEEP_ALIVE":        {"OLLAMA_KEEP_ALIVE", KeepAlive(), "The duration that models stay loaded in memory (default \"5m\")"},
-		"OLLAMA_LLM_LIBRARY":       {"OLLAMA_LLM_LIBRARY", LLMLibrary, "Set LLM library to bypass autodetection"},
+		"OLLAMA_LLM_LIBRARY":       {"OLLAMA_LLM_LIBRARY", LLMLibrary(), "Set LLM library to bypass autodetection"},
 		"OLLAMA_MAX_LOADED_MODELS": {"OLLAMA_MAX_LOADED_MODELS", MaxRunners, "Maximum number of loaded models per GPU"},
 		"OLLAMA_MAX_QUEUE":         {"OLLAMA_MAX_QUEUE", MaxQueuedRequests, "Maximum number of queued requests"},
 		"OLLAMA_MODELS":            {"OLLAMA_MODELS", Models(), "The path to the models directory"},
@@ -195,16 +242,16 @@ func AsMap() map[string]EnvVar {
 		"OLLAMA_NOPRUNE":           {"OLLAMA_NOPRUNE", NoPrune(), "Do not prune model blobs on startup"},
 		"OLLAMA_NUM_PARALLEL":      {"OLLAMA_NUM_PARALLEL", NumParallel, "Maximum number of parallel requests"},
 		"OLLAMA_ORIGINS":           {"OLLAMA_ORIGINS", Origins(), "A comma separated list of allowed origins"},
-		"OLLAMA_RUNNERS_DIR":       {"OLLAMA_RUNNERS_DIR", RunnersDir, "Location for runners"},
+		"OLLAMA_RUNNERS_DIR":       {"OLLAMA_RUNNERS_DIR", RunnersDir(), "Location for runners"},
 		"OLLAMA_SCHED_SPREAD":      {"OLLAMA_SCHED_SPREAD", SchedSpread(), "Always schedule model across all GPUs"},
-		"OLLAMA_TMPDIR":            {"OLLAMA_TMPDIR", TmpDir, "Location for temporary files"},
+		"OLLAMA_TMPDIR":            {"OLLAMA_TMPDIR", TmpDir(), "Location for temporary files"},
 	}
 	if runtime.GOOS != "darwin" {
-		ret["CUDA_VISIBLE_DEVICES"] = EnvVar{"CUDA_VISIBLE_DEVICES", CudaVisibleDevices, "Set which NVIDIA devices are visible"}
-		ret["HIP_VISIBLE_DEVICES"] = EnvVar{"HIP_VISIBLE_DEVICES", HipVisibleDevices, "Set which AMD devices are visible"}
-		ret["ROCR_VISIBLE_DEVICES"] = EnvVar{"ROCR_VISIBLE_DEVICES", RocrVisibleDevices, "Set which AMD devices are visible"}
-		ret["GPU_DEVICE_ORDINAL"] = EnvVar{"GPU_DEVICE_ORDINAL", GpuDeviceOrdinal, "Set which AMD devices are visible"}
-		ret["HSA_OVERRIDE_GFX_VERSION"] = EnvVar{"HSA_OVERRIDE_GFX_VERSION", HsaOverrideGfxVersion, "Override the gfx used for all detected AMD GPUs"}
+		ret["CUDA_VISIBLE_DEVICES"] = EnvVar{"CUDA_VISIBLE_DEVICES", CudaVisibleDevices(), "Set which NVIDIA devices are visible"}
+		ret["HIP_VISIBLE_DEVICES"] = EnvVar{"HIP_VISIBLE_DEVICES", HipVisibleDevices(), "Set which AMD devices are visible"}
+		ret["ROCR_VISIBLE_DEVICES"] = EnvVar{"ROCR_VISIBLE_DEVICES", RocrVisibleDevices(), "Set which AMD devices are visible"}
+		ret["GPU_DEVICE_ORDINAL"] = EnvVar{"GPU_DEVICE_ORDINAL", GpuDeviceOrdinal(), "Set which AMD devices are visible"}
+		ret["HSA_OVERRIDE_GFX_VERSION"] = EnvVar{"HSA_OVERRIDE_GFX_VERSION", HsaOverrideGfxVersion(), "Override the gfx used for all detected AMD GPUs"}
 		ret["OLLAMA_INTEL_GPU"] = EnvVar{"OLLAMA_INTEL_GPU", IntelGPU(), "Enable experimental Intel GPU detection"}
 	}
 	return ret
@@ -233,46 +280,6 @@ func init() {
 }
 
 func LoadConfig() {
-	RunnersDir = getenv("OLLAMA_RUNNERS_DIR")
-	if runtime.GOOS == "windows" && RunnersDir == "" {
-		// On Windows we do not carry the payloads inside the main executable
-		appExe, err := os.Executable()
-		if err != nil {
-			slog.Error("failed to lookup executable path", "error", err)
-		}
-
-		cwd, err := os.Getwd()
-		if err != nil {
-			slog.Error("failed to lookup working directory", "error", err)
-		}
-
-		var paths []string
-		for _, root := range []string{filepath.Dir(appExe), cwd} {
-			paths = append(paths,
-				root,
-				filepath.Join(root, "windows-"+runtime.GOARCH),
-				filepath.Join(root, "dist", "windows-"+runtime.GOARCH),
-			)
-		}
-
-		// Try a few variations to improve developer experience when building from source in the local tree
-		for _, p := range paths {
-			candidate := filepath.Join(p, "ollama_runners")
-			_, err := os.Stat(candidate)
-			if err == nil {
-				RunnersDir = candidate
-				break
-			}
-		}
-		if RunnersDir == "" {
-			slog.Error("unable to locate llm runner directory.  Set OLLAMA_RUNNERS_DIR to the location of 'ollama_runners'")
-		}
-	}
-
-	TmpDir = getenv("OLLAMA_TMPDIR")
-
-	LLMLibrary = getenv("OLLAMA_LLM_LIBRARY")
-
 	if onp := getenv("OLLAMA_NUM_PARALLEL"); onp != "" {
 		val, err := strconv.Atoi(onp)
 		if err != nil {
@@ -300,10 +307,4 @@ func LoadConfig() {
 			MaxQueuedRequests = p
 		}
 	}
-
-	CudaVisibleDevices = getenv("CUDA_VISIBLE_DEVICES")
-	HipVisibleDevices = getenv("HIP_VISIBLE_DEVICES")
-	RocrVisibleDevices = getenv("ROCR_VISIBLE_DEVICES")
-	GpuDeviceOrdinal = getenv("GPU_DEVICE_ORDINAL")
-	HsaOverrideGfxVersion = getenv("HSA_OVERRIDE_GFX_VERSION")
 }
diff --git a/gpu/amd_linux.go b/gpu/amd_linux.go
index 15b6fc61f..33dd03ab8 100644
--- a/gpu/amd_linux.go
+++ b/gpu/amd_linux.go
@@ -60,9 +60,9 @@ func AMDGetGPUInfo() []RocmGPUInfo {
 
 	// Determine if the user has already pre-selected which GPUs to look at, then ignore the others
 	var visibleDevices []string
-	hipVD := envconfig.HipVisibleDevices   // zero based index only
-	rocrVD := envconfig.RocrVisibleDevices // zero based index or UUID, but consumer cards seem to not support UUID
-	gpuDO := envconfig.GpuDeviceOrdinal    // zero based index
+	hipVD := envconfig.HipVisibleDevices()   // zero based index only
+	rocrVD := envconfig.RocrVisibleDevices() // zero based index or UUID, but consumer cards seem to not support UUID
+	gpuDO := envconfig.GpuDeviceOrdinal()    // zero based index
 	switch {
 	// TODO is this priorty order right?
 	case hipVD != "":
@@ -75,7 +75,7 @@ func AMDGetGPUInfo() []RocmGPUInfo {
 		visibleDevices = strings.Split(gpuDO, ",")
 	}
 
-	gfxOverride := envconfig.HsaOverrideGfxVersion
+	gfxOverride := envconfig.HsaOverrideGfxVersion()
 	var supported []string
 	libDir := ""
 
diff --git a/gpu/amd_windows.go b/gpu/amd_windows.go
index 20aed4478..a170dfdcc 100644
--- a/gpu/amd_windows.go
+++ b/gpu/amd_windows.go
@@ -53,7 +53,7 @@ func AMDGetGPUInfo() []RocmGPUInfo {
 	}
 
 	var supported []string
-	gfxOverride := envconfig.HsaOverrideGfxVersion
+	gfxOverride := envconfig.HsaOverrideGfxVersion()
 	if gfxOverride == "" {
 		supported, err = GetSupportedGFX(libDir)
 		if err != nil {
diff --git a/gpu/assets.go b/gpu/assets.go
index 073d2e813..39ff7c21a 100644
--- a/gpu/assets.go
+++ b/gpu/assets.go
@@ -26,7 +26,7 @@ func PayloadsDir() (string, error) {
 	defer lock.Unlock()
 	var err error
 	if payloadsDir == "" {
-		runnersDir := envconfig.RunnersDir
+		runnersDir := envconfig.RunnersDir()
 
 		if runnersDir != "" {
 			payloadsDir = runnersDir
@@ -35,7 +35,7 @@ func PayloadsDir() (string, error) {
 
 		// The remainder only applies on non-windows where we still carry payloads in the main executable
 		cleanupTmpDirs()
-		tmpDir := envconfig.TmpDir
+		tmpDir := envconfig.TmpDir()
 		if tmpDir == "" {
 			tmpDir, err = os.MkdirTemp("", "ollama")
 			if err != nil {
@@ -105,7 +105,7 @@ func cleanupTmpDirs() {
 func Cleanup() {
 	lock.Lock()
 	defer lock.Unlock()
-	runnersDir := envconfig.RunnersDir
+	runnersDir := envconfig.RunnersDir()
 	if payloadsDir != "" && runnersDir == "" && runtime.GOOS != "windows" {
 		// We want to fully clean up the tmpdir parent of the payloads dir
 		tmpDir := filepath.Clean(filepath.Join(payloadsDir, ".."))
diff --git a/gpu/gpu.go b/gpu/gpu.go
index c30595427..acab1c8dd 100644
--- a/gpu/gpu.go
+++ b/gpu/gpu.go
@@ -230,8 +230,8 @@ func GetGPUInfo() GpuInfoList {
 
 		// On windows we bundle the nvidia library one level above the runner dir
 		depPath := ""
-		if runtime.GOOS == "windows" && envconfig.RunnersDir != "" {
-			depPath = filepath.Join(filepath.Dir(envconfig.RunnersDir), "cuda")
+		if runtime.GOOS == "windows" && envconfig.RunnersDir() != "" {
+			depPath = filepath.Join(filepath.Dir(envconfig.RunnersDir()), "cuda")
 		}
 
 		// Load ALL libraries
@@ -306,8 +306,8 @@ func GetGPUInfo() GpuInfoList {
 			oHandles = initOneAPIHandles()
 			// On windows we bundle the oneapi library one level above the runner dir
 			depPath = ""
-			if runtime.GOOS == "windows" && envconfig.RunnersDir != "" {
-				depPath = filepath.Join(filepath.Dir(envconfig.RunnersDir), "oneapi")
+			if runtime.GOOS == "windows" && envconfig.RunnersDir() != "" {
+				depPath = filepath.Join(filepath.Dir(envconfig.RunnersDir()), "oneapi")
 			}
 
 			for d := range oHandles.oneapi.num_drivers {
diff --git a/llm/server.go b/llm/server.go
index 84d9e93a4..0741d3861 100644
--- a/llm/server.go
+++ b/llm/server.go
@@ -163,7 +163,7 @@ func NewLlamaServer(gpus gpu.GpuInfoList, model string, ggml *GGML, adapters, pr
 	} else {
 		servers = serversForGpu(gpus[0]) // All GPUs in the list are matching Library and Variant
 	}
-	demandLib := envconfig.LLMLibrary
+	demandLib := envconfig.LLMLibrary()
 	if demandLib != "" {
 		serverPath := availableServers[demandLib]
 		if serverPath == "" {

From 0f1910129f0a73c469ce2c012d39c8d98b79ef80 Mon Sep 17 00:00:00 2001
From: Michael Yang 
Date: Wed, 3 Jul 2024 19:41:17 -0700
Subject: [PATCH 131/384] int

---
 envconfig/config.go           | 66 ++++++++++-------------------------
 integration/basic_test.go     |  9 +----
 integration/max_queue_test.go | 14 ++++----
 server/sched.go               | 23 +++++++-----
 server/sched_test.go          |  7 ++--
 5 files changed, 42 insertions(+), 77 deletions(-)

diff --git a/envconfig/config.go b/envconfig/config.go
index 34cc4dac5..01abea421 100644
--- a/envconfig/config.go
+++ b/envconfig/config.go
@@ -213,13 +213,22 @@ func RunnersDir() (p string) {
 	return p
 }
 
+func Int(k string, n int) func() int {
+	return func() int {
+		if s := getenv(k); s != "" {
+			if n, err := strconv.ParseInt(s, 10, 64); err == nil && n >= 0 {
+				return int(n)
+			}
+		}
+
+		return n
+	}
+}
+
 var (
-	// Set via OLLAMA_MAX_LOADED_MODELS in the environment
-	MaxRunners int
-	// Set via OLLAMA_MAX_QUEUE in the environment
-	MaxQueuedRequests int
-	// Set via OLLAMA_NUM_PARALLEL in the environment
-	NumParallel int
+	NumParallel = Int("OLLAMA_NUM_PARALLEL", 0)
+	MaxRunners  = Int("OLLAMA_MAX_LOADED_MODELS", 0)
+	MaxQueue    = Int("OLLAMA_MAX_QUEUE", 512)
 )
 
 type EnvVar struct {
@@ -235,12 +244,12 @@ func AsMap() map[string]EnvVar {
 		"OLLAMA_HOST":              {"OLLAMA_HOST", Host(), "IP Address for the ollama server (default 127.0.0.1:11434)"},
 		"OLLAMA_KEEP_ALIVE":        {"OLLAMA_KEEP_ALIVE", KeepAlive(), "The duration that models stay loaded in memory (default \"5m\")"},
 		"OLLAMA_LLM_LIBRARY":       {"OLLAMA_LLM_LIBRARY", LLMLibrary(), "Set LLM library to bypass autodetection"},
-		"OLLAMA_MAX_LOADED_MODELS": {"OLLAMA_MAX_LOADED_MODELS", MaxRunners, "Maximum number of loaded models per GPU"},
-		"OLLAMA_MAX_QUEUE":         {"OLLAMA_MAX_QUEUE", MaxQueuedRequests, "Maximum number of queued requests"},
+		"OLLAMA_MAX_LOADED_MODELS": {"OLLAMA_MAX_LOADED_MODELS", MaxRunners(), "Maximum number of loaded models per GPU"},
+		"OLLAMA_MAX_QUEUE":         {"OLLAMA_MAX_QUEUE", MaxQueue(), "Maximum number of queued requests"},
 		"OLLAMA_MODELS":            {"OLLAMA_MODELS", Models(), "The path to the models directory"},
 		"OLLAMA_NOHISTORY":         {"OLLAMA_NOHISTORY", NoHistory(), "Do not preserve readline history"},
 		"OLLAMA_NOPRUNE":           {"OLLAMA_NOPRUNE", NoPrune(), "Do not prune model blobs on startup"},
-		"OLLAMA_NUM_PARALLEL":      {"OLLAMA_NUM_PARALLEL", NumParallel, "Maximum number of parallel requests"},
+		"OLLAMA_NUM_PARALLEL":      {"OLLAMA_NUM_PARALLEL", NumParallel(), "Maximum number of parallel requests"},
 		"OLLAMA_ORIGINS":           {"OLLAMA_ORIGINS", Origins(), "A comma separated list of allowed origins"},
 		"OLLAMA_RUNNERS_DIR":       {"OLLAMA_RUNNERS_DIR", RunnersDir(), "Location for runners"},
 		"OLLAMA_SCHED_SPREAD":      {"OLLAMA_SCHED_SPREAD", SchedSpread(), "Always schedule model across all GPUs"},
@@ -269,42 +278,3 @@ func Values() map[string]string {
 func getenv(key string) string {
 	return strings.Trim(os.Getenv(key), "\"' ")
 }
-
-func init() {
-	// default values
-	NumParallel = 0 // Autoselect
-	MaxRunners = 0  // Autoselect
-	MaxQueuedRequests = 512
-
-	LoadConfig()
-}
-
-func LoadConfig() {
-	if onp := getenv("OLLAMA_NUM_PARALLEL"); onp != "" {
-		val, err := strconv.Atoi(onp)
-		if err != nil {
-			slog.Error("invalid setting, ignoring", "OLLAMA_NUM_PARALLEL", onp, "error", err)
-		} else {
-			NumParallel = val
-		}
-	}
-
-	maxRunners := getenv("OLLAMA_MAX_LOADED_MODELS")
-	if maxRunners != "" {
-		m, err := strconv.Atoi(maxRunners)
-		if err != nil {
-			slog.Error("invalid setting, ignoring", "OLLAMA_MAX_LOADED_MODELS", maxRunners, "error", err)
-		} else {
-			MaxRunners = m
-		}
-	}
-
-	if onp := os.Getenv("OLLAMA_MAX_QUEUE"); onp != "" {
-		p, err := strconv.Atoi(onp)
-		if err != nil || p <= 0 {
-			slog.Error("invalid setting, ignoring", "OLLAMA_MAX_QUEUE", onp, "error", err)
-		} else {
-			MaxQueuedRequests = p
-		}
-	}
-}
diff --git a/integration/basic_test.go b/integration/basic_test.go
index 6e632a1ce..8e35b5c5b 100644
--- a/integration/basic_test.go
+++ b/integration/basic_test.go
@@ -45,14 +45,7 @@ func TestUnicodeModelDir(t *testing.T) {
 	defer os.RemoveAll(modelDir)
 	slog.Info("unicode", "OLLAMA_MODELS", modelDir)
 
-	oldModelsDir := os.Getenv("OLLAMA_MODELS")
-	if oldModelsDir == "" {
-		defer os.Unsetenv("OLLAMA_MODELS")
-	} else {
-		defer os.Setenv("OLLAMA_MODELS", oldModelsDir)
-	}
-	err = os.Setenv("OLLAMA_MODELS", modelDir)
-	require.NoError(t, err)
+	t.Setenv("OLLAMA_MODELS", modelDir)
 
 	ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute)
 	defer cancel()
diff --git a/integration/max_queue_test.go b/integration/max_queue_test.go
index dfa5eae0c..b06197e1f 100644
--- a/integration/max_queue_test.go
+++ b/integration/max_queue_test.go
@@ -5,7 +5,6 @@ package integration
 import (
 	"context"
 	"errors"
-	"fmt"
 	"log/slog"
 	"os"
 	"strconv"
@@ -14,8 +13,10 @@ import (
 	"testing"
 	"time"
 
-	"github.com/ollama/ollama/api"
 	"github.com/stretchr/testify/require"
+
+	"github.com/ollama/ollama/api"
+	"github.com/ollama/ollama/envconfig"
 )
 
 func TestMaxQueue(t *testing.T) {
@@ -27,13 +28,10 @@ func TestMaxQueue(t *testing.T) {
 	// Note: This test can be quite slow when running in CPU mode, so keep the threadCount low unless your on GPU
 	// Also note that by default Darwin can't sustain > ~128 connections without adjusting limits
 	threadCount := 32
-	mq := os.Getenv("OLLAMA_MAX_QUEUE")
-	if mq != "" {
-		var err error
-		threadCount, err = strconv.Atoi(mq)
-		require.NoError(t, err)
+	if maxQueue := envconfig.MaxQueue(); maxQueue != 0 {
+		threadCount = maxQueue
 	} else {
-		os.Setenv("OLLAMA_MAX_QUEUE", fmt.Sprintf("%d", threadCount))
+		t.Setenv("OLLAMA_MAX_QUEUE", strconv.Itoa(threadCount))
 	}
 
 	req := api.GenerateRequest{
diff --git a/server/sched.go b/server/sched.go
index ad40c4ef0..610a2c50f 100644
--- a/server/sched.go
+++ b/server/sched.go
@@ -5,9 +5,11 @@ import (
 	"errors"
 	"fmt"
 	"log/slog"
+	"os"
 	"reflect"
 	"runtime"
 	"sort"
+	"strconv"
 	"strings"
 	"sync"
 	"time"
@@ -59,11 +61,12 @@ var defaultParallel = 4
 var ErrMaxQueue = fmt.Errorf("server busy, please try again.  maximum pending requests exceeded")
 
 func InitScheduler(ctx context.Context) *Scheduler {
+	maxQueue := envconfig.MaxQueue()
 	sched := &Scheduler{
-		pendingReqCh:  make(chan *LlmRequest, envconfig.MaxQueuedRequests),
-		finishedReqCh: make(chan *LlmRequest, envconfig.MaxQueuedRequests),
-		expiredCh:     make(chan *runnerRef, envconfig.MaxQueuedRequests),
-		unloadedCh:    make(chan interface{}, envconfig.MaxQueuedRequests),
+		pendingReqCh:  make(chan *LlmRequest, maxQueue),
+		finishedReqCh: make(chan *LlmRequest, maxQueue),
+		expiredCh:     make(chan *runnerRef, maxQueue),
+		unloadedCh:    make(chan interface{}, maxQueue),
 		loaded:        make(map[string]*runnerRef),
 		newServerFn:   llm.NewLlamaServer,
 		getGpuFn:      gpu.GetGPUInfo,
@@ -126,7 +129,7 @@ func (s *Scheduler) processPending(ctx context.Context) {
 				slog.Debug("pending request cancelled or timed out, skipping scheduling")
 				continue
 			}
-			numParallel := envconfig.NumParallel
+			numParallel := envconfig.NumParallel()
 			// TODO (jmorganca): multimodal models don't support parallel yet
 			// see https://github.com/ollama/ollama/issues/4165
 			if len(pending.model.ProjectorPaths) > 0 && numParallel != 1 {
@@ -148,7 +151,7 @@ func (s *Scheduler) processPending(ctx context.Context) {
 						pending.useLoadedRunner(runner, s.finishedReqCh)
 						break
 					}
-				} else if envconfig.MaxRunners > 0 && loadedCount >= envconfig.MaxRunners {
+				} else if envconfig.MaxRunners() > 0 && loadedCount >= envconfig.MaxRunners() {
 					slog.Debug("max runners achieved, unloading one to make room", "runner_count", loadedCount)
 					runnerToExpire = s.findRunnerToUnload()
 				} else {
@@ -161,7 +164,7 @@ func (s *Scheduler) processPending(ctx context.Context) {
 						gpus = s.getGpuFn()
 					}
 
-					if envconfig.MaxRunners <= 0 {
+					if envconfig.MaxRunners() <= 0 {
 						// No user specified MaxRunners, so figure out what automatic setting to use
 						// If all GPUs have reliable free memory reporting, defaultModelsPerGPU * the number of GPUs
 						// if any GPU has unreliable free memory reporting, 1x the number of GPUs
@@ -173,11 +176,13 @@ func (s *Scheduler) processPending(ctx context.Context) {
 							}
 						}
 						if allReliable {
-							envconfig.MaxRunners = defaultModelsPerGPU * len(gpus)
+							// HACK
+							os.Setenv("OLLAMA_MAX_LOADED_MODELS", strconv.Itoa(defaultModelsPerGPU*len(gpus)))
 							slog.Debug("updating default concurrency", "OLLAMA_MAX_LOADED_MODELS", envconfig.MaxRunners, "gpu_count", len(gpus))
 						} else {
+							// HACK
+							os.Setenv("OLLAMA_MAX_LOADED_MODELS", strconv.Itoa(len(gpus)))
 							slog.Info("one or more GPUs detected that are unable to accurately report free memory - disabling default concurrency")
-							envconfig.MaxRunners = len(gpus)
 						}
 					}
 
diff --git a/server/sched_test.go b/server/sched_test.go
index 9ddd1fabe..3166ff66c 100644
--- a/server/sched_test.go
+++ b/server/sched_test.go
@@ -12,7 +12,6 @@ import (
 
 	"github.com/ollama/ollama/api"
 	"github.com/ollama/ollama/app/lifecycle"
-	"github.com/ollama/ollama/envconfig"
 	"github.com/ollama/ollama/format"
 	"github.com/ollama/ollama/gpu"
 	"github.com/ollama/ollama/llm"
@@ -272,7 +271,7 @@ func TestRequestsMultipleLoadedModels(t *testing.T) {
 	c.req.opts.NumGPU = 0                                       // CPU load, will be allowed
 	d := newScenarioRequest(t, ctx, "ollama-model-3c", 30, nil) // Needs prior unloaded
 
-	envconfig.MaxRunners = 1
+	t.Setenv("OLLAMA_MAX_LOADED_MODELS", "1")
 	s.newServerFn = a.newServer
 	slog.Info("a")
 	s.pendingReqCh <- a.req
@@ -291,7 +290,7 @@ func TestRequestsMultipleLoadedModels(t *testing.T) {
 	require.Len(t, s.loaded, 1)
 	s.loadedMu.Unlock()
 
-	envconfig.MaxRunners = 0
+	t.Setenv("OLLAMA_MAX_LOADED_MODELS", "0")
 	s.newServerFn = b.newServer
 	slog.Info("b")
 	s.pendingReqCh <- b.req
@@ -362,7 +361,7 @@ func TestGetRunner(t *testing.T) {
 	a := newScenarioRequest(t, ctx, "ollama-model-1a", 10, &api.Duration{Duration: 2 * time.Millisecond})
 	b := newScenarioRequest(t, ctx, "ollama-model-1b", 10, &api.Duration{Duration: 2 * time.Millisecond})
 	c := newScenarioRequest(t, ctx, "ollama-model-1c", 10, &api.Duration{Duration: 2 * time.Millisecond})
-	envconfig.MaxQueuedRequests = 1
+	t.Setenv("OLLAMA_MAX_QUEUE", "1")
 	s := InitScheduler(ctx)
 	s.getGpuFn = getGpuFn
 	s.getCpuFn = getCpuFn

From 1954ec5917bf81ac743ba19bf0e7a6da47766778 Mon Sep 17 00:00:00 2001
From: Michael Yang 
Date: Wed, 3 Jul 2024 19:43:17 -0700
Subject: [PATCH 132/384] uint64

---
 api/client_test.go              |  3 --
 integration/concurrency_test.go | 70 +++++++++++++++++----------------
 server/manifest_test.go         |  2 -
 server/modelpath_test.go        |  3 --
 server/routes_create_test.go    | 10 -----
 server/routes_delete_test.go    |  2 -
 server/routes_list_test.go      |  2 -
 server/routes_test.go           |  4 --
 8 files changed, 37 insertions(+), 59 deletions(-)

diff --git a/api/client_test.go b/api/client_test.go
index fe9fd74f7..23fe9334b 100644
--- a/api/client_test.go
+++ b/api/client_test.go
@@ -2,8 +2,6 @@ package api
 
 import (
 	"testing"
-
-	"github.com/ollama/ollama/envconfig"
 )
 
 func TestClientFromEnvironment(t *testing.T) {
@@ -33,7 +31,6 @@ func TestClientFromEnvironment(t *testing.T) {
 	for k, v := range testCases {
 		t.Run(k, func(t *testing.T) {
 			t.Setenv("OLLAMA_HOST", v.value)
-			envconfig.LoadConfig()
 
 			client, err := ClientFromEnvironment()
 			if err != v.err {
diff --git a/integration/concurrency_test.go b/integration/concurrency_test.go
index 8593285b4..81d0b5878 100644
--- a/integration/concurrency_test.go
+++ b/integration/concurrency_test.go
@@ -5,14 +5,16 @@ package integration
 import (
 	"context"
 	"log/slog"
-	"os"
 	"strconv"
 	"sync"
 	"testing"
 	"time"
 
-	"github.com/ollama/ollama/api"
 	"github.com/stretchr/testify/require"
+
+	"github.com/ollama/ollama/api"
+	"github.com/ollama/ollama/envconfig"
+	"github.com/ollama/ollama/format"
 )
 
 func TestMultiModelConcurrency(t *testing.T) {
@@ -106,13 +108,16 @@ func TestIntegrationConcurrentPredictOrcaMini(t *testing.T) {
 
 // Stress the system if we know how much VRAM it has, and attempt to load more models than will fit
 func TestMultiModelStress(t *testing.T) {
-	vram := os.Getenv("OLLAMA_MAX_VRAM") // TODO - discover actual VRAM
-	if vram == "" {
+	s := os.Getenv("OLLAMA_MAX_VRAM") // TODO - discover actual VRAM
+	if s == "" {
 		t.Skip("OLLAMA_MAX_VRAM not specified, can't pick the right models for the stress test")
 	}
-	max, err := strconv.ParseUint(vram, 10, 64)
-	require.NoError(t, err)
-	const MB = uint64(1024 * 1024)
+
+	maxVram, err := strconv.ParseUint(s, 10, 64)
+	if err != nil {
+		t.Fatal(err)
+	}
+
 	type model struct {
 		name string
 		size uint64 // Approximate amount of VRAM they typically use when fully loaded in VRAM
@@ -121,83 +126,82 @@ func TestMultiModelStress(t *testing.T) {
 	smallModels := []model{
 		{
 			name: "orca-mini",
-			size: 2992 * MB,
+			size: 2992 * format.MebiByte,
 		},
 		{
 			name: "phi",
-			size: 2616 * MB,
+			size: 2616 * format.MebiByte,
 		},
 		{
 			name: "gemma:2b",
-			size: 2364 * MB,
+			size: 2364 * format.MebiByte,
 		},
 		{
 			name: "stable-code:3b",
-			size: 2608 * MB,
+			size: 2608 * format.MebiByte,
 		},
 		{
 			name: "starcoder2:3b",
-			size: 2166 * MB,
+			size: 2166 * format.MebiByte,
 		},
 	}
 	mediumModels := []model{
 		{
 			name: "llama2",
-			size: 5118 * MB,
+			size: 5118 * format.MebiByte,
 		},
 		{
 			name: "mistral",
-			size: 4620 * MB,
+			size: 4620 * format.MebiByte,
 		},
 		{
 			name: "orca-mini:7b",
-			size: 5118 * MB,
+			size: 5118 * format.MebiByte,
 		},
 		{
 			name: "dolphin-mistral",
-			size: 4620 * MB,
+			size: 4620 * format.MebiByte,
 		},
 		{
 			name: "gemma:7b",
-			size: 5000 * MB,
+			size: 5000 * format.MebiByte,
+		},
+		{
+			name: "codellama:7b",
+			size: 5118 * format.MebiByte,
 		},
-		// TODO - uncomment this once #3565 is merged and this is rebased on it
-		// {
-		// 	name: "codellama:7b",
-		// 	size: 5118 * MB,
-		// },
 	}
 
 	// These seem to be too slow to be useful...
 	// largeModels := []model{
 	// 	{
 	// 		name: "llama2:13b",
-	// 		size: 7400 * MB,
+	// 		size: 7400 * format.MebiByte,
 	// 	},
 	// 	{
 	// 		name: "codellama:13b",
-	// 		size: 7400 * MB,
+	// 		size: 7400 * format.MebiByte,
 	// 	},
 	// 	{
 	// 		name: "orca-mini:13b",
-	// 		size: 7400 * MB,
+	// 		size: 7400 * format.MebiByte,
 	// 	},
 	// 	{
 	// 		name: "gemma:7b",
-	// 		size: 5000 * MB,
+	// 		size: 5000 * format.MebiByte,
 	// 	},
 	// 	{
 	// 		name: "starcoder2:15b",
-	// 		size: 9100 * MB,
+	// 		size: 9100 * format.MebiByte,
 	// 	},
 	// }
 
 	var chosenModels []model
 	switch {
-	case max < 10000*MB:
+	case maxVram < 10000*format.MebiByte:
 		slog.Info("selecting small models")
 		chosenModels = smallModels
-	// case max < 30000*MB:
+	// case maxVram < 30000*format.MebiByte:
 	default:
 		slog.Info("selecting medium models")
 		chosenModels = mediumModels
@@ -226,15 +230,15 @@ func TestMultiModelStress(t *testing.T) {
 	}
 
 	var wg sync.WaitGroup
-	consumed := uint64(256 * MB) // Assume some baseline usage
+	consumed := uint64(256 * format.MebiByte) // Assume some baseline usage
 	for i := 0; i < len(req); i++ {
 		// Always get at least 2 models, but dont' overshoot VRAM too much or we'll take too long
-		if i > 1 && consumed > max {
-			slog.Info("achieved target vram exhaustion", "count", i, "vramMB", max/1024/1024, "modelsMB", consumed/1024/1024)
+		if i > 1 && consumed > vram {
+			slog.Info("achieved target vram exhaustion", "count", i, "vram", format.HumanBytes2(vram), "models", format.HumanBytes2(consumed))
 			break
 		}
 		consumed += chosenModels[i].size
-		slog.Info("target vram", "count", i, "vramMB", max/1024/1024, "modelsMB", consumed/1024/1024)
+		slog.Info("target vram", "count", i, "vram", format.HumanBytes2(vram), "models", format.HumanBytes2(consumed))
 
 		wg.Add(1)
 		go func(i int) {
diff --git a/server/manifest_test.go b/server/manifest_test.go
index ca6c3d2e9..a4af5d5e0 100644
--- a/server/manifest_test.go
+++ b/server/manifest_test.go
@@ -7,7 +7,6 @@ import (
 	"slices"
 	"testing"
 
-	"github.com/ollama/ollama/envconfig"
 	"github.com/ollama/ollama/types/model"
 )
 
@@ -108,7 +107,6 @@ func TestManifests(t *testing.T) {
 		t.Run(n, func(t *testing.T) {
 			d := t.TempDir()
 			t.Setenv("OLLAMA_MODELS", d)
-			envconfig.LoadConfig()
 
 			for _, p := range wants.ps {
 				createManifest(t, d, p)
diff --git a/server/modelpath_test.go b/server/modelpath_test.go
index 6c4dfbee4..849e0fa73 100644
--- a/server/modelpath_test.go
+++ b/server/modelpath_test.go
@@ -7,8 +7,6 @@ import (
 
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
-
-	"github.com/ollama/ollama/envconfig"
 )
 
 func TestGetBlobsPath(t *testing.T) {
@@ -63,7 +61,6 @@ func TestGetBlobsPath(t *testing.T) {
 	for _, tc := range tests {
 		t.Run(tc.name, func(t *testing.T) {
 			t.Setenv("OLLAMA_MODELS", dir)
-			envconfig.LoadConfig()
 
 			got, err := GetBlobsPath(tc.digest)
 
diff --git a/server/routes_create_test.go b/server/routes_create_test.go
index 3234ea5e1..c853a9e98 100644
--- a/server/routes_create_test.go
+++ b/server/routes_create_test.go
@@ -15,7 +15,6 @@ import (
 
 	"github.com/gin-gonic/gin"
 	"github.com/ollama/ollama/api"
-	"github.com/ollama/ollama/envconfig"
 	"github.com/ollama/ollama/llm"
 )
 
@@ -89,7 +88,6 @@ func TestCreateFromBin(t *testing.T) {
 
 	p := t.TempDir()
 	t.Setenv("OLLAMA_MODELS", p)
-	envconfig.LoadConfig()
 
 	var s Server
 	w := createRequest(t, s.CreateModelHandler, api.CreateRequest{
@@ -117,7 +115,6 @@ func TestCreateFromModel(t *testing.T) {
 
 	p := t.TempDir()
 	t.Setenv("OLLAMA_MODELS", p)
-	envconfig.LoadConfig()
 	var s Server
 
 	w := createRequest(t, s.CreateModelHandler, api.CreateRequest{
@@ -160,7 +157,6 @@ func TestCreateRemovesLayers(t *testing.T) {
 
 	p := t.TempDir()
 	t.Setenv("OLLAMA_MODELS", p)
-	envconfig.LoadConfig()
 	var s Server
 
 	w := createRequest(t, s.CreateModelHandler, api.CreateRequest{
@@ -209,7 +205,6 @@ func TestCreateUnsetsSystem(t *testing.T) {
 
 	p := t.TempDir()
 	t.Setenv("OLLAMA_MODELS", p)
-	envconfig.LoadConfig()
 	var s Server
 
 	w := createRequest(t, s.CreateModelHandler, api.CreateRequest{
@@ -267,7 +262,6 @@ func TestCreateMergeParameters(t *testing.T) {
 
 	p := t.TempDir()
 	t.Setenv("OLLAMA_MODELS", p)
-	envconfig.LoadConfig()
 	var s Server
 
 	w := createRequest(t, s.CreateModelHandler, api.CreateRequest{
@@ -372,7 +366,6 @@ func TestCreateReplacesMessages(t *testing.T) {
 
 	p := t.TempDir()
 	t.Setenv("OLLAMA_MODELS", p)
-	envconfig.LoadConfig()
 	var s Server
 
 	w := createRequest(t, s.CreateModelHandler, api.CreateRequest{
@@ -450,7 +443,6 @@ func TestCreateTemplateSystem(t *testing.T) {
 
 	p := t.TempDir()
 	t.Setenv("OLLAMA_MODELS", p)
-	envconfig.LoadConfig()
 	var s Server
 
 	w := createRequest(t, s.CreateModelHandler, api.CreateRequest{
@@ -534,7 +526,6 @@ func TestCreateLicenses(t *testing.T) {
 
 	p := t.TempDir()
 	t.Setenv("OLLAMA_MODELS", p)
-	envconfig.LoadConfig()
 	var s Server
 
 	w := createRequest(t, s.CreateModelHandler, api.CreateRequest{
@@ -582,7 +573,6 @@ func TestCreateDetectTemplate(t *testing.T) {
 
 	p := t.TempDir()
 	t.Setenv("OLLAMA_MODELS", p)
-	envconfig.LoadConfig()
 	var s Server
 
 	t.Run("matched", func(t *testing.T) {
diff --git a/server/routes_delete_test.go b/server/routes_delete_test.go
index 33a97a73d..2354d730a 100644
--- a/server/routes_delete_test.go
+++ b/server/routes_delete_test.go
@@ -10,7 +10,6 @@ import (
 
 	"github.com/gin-gonic/gin"
 	"github.com/ollama/ollama/api"
-	"github.com/ollama/ollama/envconfig"
 	"github.com/ollama/ollama/types/model"
 )
 
@@ -19,7 +18,6 @@ func TestDelete(t *testing.T) {
 
 	p := t.TempDir()
 	t.Setenv("OLLAMA_MODELS", p)
-	envconfig.LoadConfig()
 
 	var s Server
 
diff --git a/server/routes_list_test.go b/server/routes_list_test.go
index c2d9c1137..29e3214c5 100644
--- a/server/routes_list_test.go
+++ b/server/routes_list_test.go
@@ -9,14 +9,12 @@ import (
 
 	"github.com/gin-gonic/gin"
 	"github.com/ollama/ollama/api"
-	"github.com/ollama/ollama/envconfig"
 )
 
 func TestList(t *testing.T) {
 	gin.SetMode(gin.TestMode)
 
 	t.Setenv("OLLAMA_MODELS", t.TempDir())
-	envconfig.LoadConfig()
 
 	expectNames := []string{
 		"mistral:7b-instruct-q4_0",
diff --git a/server/routes_test.go b/server/routes_test.go
index 97786ba2b..17da23054 100644
--- a/server/routes_test.go
+++ b/server/routes_test.go
@@ -19,7 +19,6 @@ import (
 	"github.com/stretchr/testify/require"
 
 	"github.com/ollama/ollama/api"
-	"github.com/ollama/ollama/envconfig"
 	"github.com/ollama/ollama/llm"
 	"github.com/ollama/ollama/openai"
 	"github.com/ollama/ollama/parser"
@@ -347,7 +346,6 @@ func Test_Routes(t *testing.T) {
 	}
 
 	t.Setenv("OLLAMA_MODELS", t.TempDir())
-	envconfig.LoadConfig()
 
 	s := &Server{}
 	router := s.GenerateRoutes()
@@ -378,7 +376,6 @@ func Test_Routes(t *testing.T) {
 
 func TestCase(t *testing.T) {
 	t.Setenv("OLLAMA_MODELS", t.TempDir())
-	envconfig.LoadConfig()
 
 	cases := []string{
 		"mistral",
@@ -458,7 +455,6 @@ func TestCase(t *testing.T) {
 
 func TestShow(t *testing.T) {
 	t.Setenv("OLLAMA_MODELS", t.TempDir())
-	envconfig.LoadConfig()
 
 	var s Server
 

From 78140a712ce8feac6fad2ae2c0043056f1a47fdc Mon Sep 17 00:00:00 2001
From: Michael Yang 
Date: Fri, 5 Jul 2024 16:52:01 -0700
Subject: [PATCH 133/384] cleanup tests

---
 envconfig/config_test.go | 15 ---------------
 1 file changed, 15 deletions(-)

diff --git a/envconfig/config_test.go b/envconfig/config_test.go
index 87c808ca9..977298aaf 100644
--- a/envconfig/config_test.go
+++ b/envconfig/config_test.go
@@ -6,23 +6,8 @@ import (
 	"time"
 
 	"github.com/google/go-cmp/cmp"
-	"github.com/stretchr/testify/require"
 )
 
-func TestSmoke(t *testing.T) {
-	t.Setenv("OLLAMA_DEBUG", "")
-	require.False(t, Debug())
-
-	t.Setenv("OLLAMA_DEBUG", "false")
-	require.False(t, Debug())
-
-	t.Setenv("OLLAMA_DEBUG", "1")
-	require.True(t, Debug())
-
-	t.Setenv("OLLAMA_FLASH_ATTENTION", "1")
-	require.True(t, FlashAttention())
-}
-
 func TestHost(t *testing.T) {
 	cases := map[string]struct {
 		value  string

From 85d9d73a7253fce232208a2355113c8ae6d69353 Mon Sep 17 00:00:00 2001
From: Michael Yang 
Date: Mon, 8 Jul 2024 10:34:12 -0700
Subject: [PATCH 134/384] comments

---
 envconfig/config.go      | 50 ++++++++++++++------------
 envconfig/config_test.go | 77 +++++++++++++++++++++++++++++++---------
 server/sched.go          |  4 +--
 3 files changed, 90 insertions(+), 41 deletions(-)

diff --git a/envconfig/config.go b/envconfig/config.go
index 01abea421..b82b773d4 100644
--- a/envconfig/config.go
+++ b/envconfig/config.go
@@ -1,7 +1,6 @@
 package envconfig
 
 import (
-	"errors"
 	"fmt"
 	"log/slog"
 	"math"
@@ -15,15 +14,12 @@ import (
 	"time"
 )
 
-var ErrInvalidHostPort = errors.New("invalid port specified in OLLAMA_HOST")
-
 // Host returns the scheme and host. Host can be configured via the OLLAMA_HOST environment variable.
 // Default is scheme "http" and host "127.0.0.1:11434"
 func Host() *url.URL {
 	defaultPort := "11434"
 
-	s := os.Getenv("OLLAMA_HOST")
-	s = strings.TrimSpace(strings.Trim(strings.TrimSpace(s), "\"'"))
+	s := strings.TrimSpace(Var("OLLAMA_HOST"))
 	scheme, hostport, ok := strings.Cut(s, "://")
 	switch {
 	case !ok:
@@ -48,6 +44,7 @@ func Host() *url.URL {
 	}
 
 	if n, err := strconv.ParseInt(port, 10, 32); err != nil || n > 65535 || n < 0 {
+		slog.Warn("invalid port, using default", "port", port, "default", defaultPort)
 		return &url.URL{
 			Scheme: scheme,
 			Host:   net.JoinHostPort(host, defaultPort),
@@ -62,7 +59,7 @@ func Host() *url.URL {
 
 // Origins returns a list of allowed origins. Origins can be configured via the OLLAMA_ORIGINS environment variable.
 func Origins() (origins []string) {
-	if s := getenv("OLLAMA_ORIGINS"); s != "" {
+	if s := Var("OLLAMA_ORIGINS"); s != "" {
 		origins = strings.Split(s, ",")
 	}
 
@@ -87,7 +84,7 @@ func Origins() (origins []string) {
 // Models returns the path to the models directory. Models directory can be configured via the OLLAMA_MODELS environment variable.
 // Default is $HOME/.ollama/models
 func Models() string {
-	if s, ok := os.LookupEnv("OLLAMA_MODELS"); ok {
+	if s := Var("OLLAMA_MODELS"); s != "" {
 		return s
 	}
 
@@ -104,7 +101,7 @@ func Models() string {
 // Default is 5 minutes.
 func KeepAlive() (keepAlive time.Duration) {
 	keepAlive = 5 * time.Minute
-	if s := os.Getenv("OLLAMA_KEEP_ALIVE"); s != "" {
+	if s := Var("OLLAMA_KEEP_ALIVE"); s != "" {
 		if d, err := time.ParseDuration(s); err == nil {
 			keepAlive = d
 		} else if n, err := strconv.ParseInt(s, 10, 64); err == nil {
@@ -121,7 +118,7 @@ func KeepAlive() (keepAlive time.Duration) {
 
 func Bool(k string) func() bool {
 	return func() bool {
-		if s := getenv(k); s != "" {
+		if s := Var(k); s != "" {
 			b, err := strconv.ParseBool(s)
 			if err != nil {
 				return true
@@ -151,7 +148,7 @@ var (
 
 func String(s string) func() string {
 	return func() string {
-		return getenv(s)
+		return Var(s)
 	}
 }
 
@@ -167,7 +164,7 @@ var (
 )
 
 func RunnersDir() (p string) {
-	if p := getenv("OLLAMA_RUNNERS_DIR"); p != "" {
+	if p := Var("OLLAMA_RUNNERS_DIR"); p != "" {
 		return p
 	}
 
@@ -213,22 +210,29 @@ func RunnersDir() (p string) {
 	return p
 }
 
-func Int(k string, n int) func() int {
-	return func() int {
-		if s := getenv(k); s != "" {
-			if n, err := strconv.ParseInt(s, 10, 64); err == nil && n >= 0 {
-				return int(n)
+func Uint(key string, defaultValue uint) func() uint {
+	return func() uint {
+		if s := Var(key); s != "" {
+			if n, err := strconv.ParseUint(s, 10, 64); err != nil {
+				slog.Warn("invalid environment variable, using default", "key", key, "value", s, "default", defaultValue)
+			} else {
+				return uint(n)
 			}
 		}
 
-		return n
+		return defaultValue
 	}
 }
 
 var (
-	NumParallel = Int("OLLAMA_NUM_PARALLEL", 0)
-	MaxRunners  = Int("OLLAMA_MAX_LOADED_MODELS", 0)
-	MaxQueue    = Int("OLLAMA_MAX_QUEUE", 512)
+	// NumParallel sets the number of parallel model requests. NumParallel can be configured via the OLLAMA_NUM_PARALLEL environment variable.
+	NumParallel = Uint("OLLAMA_NUM_PARALLEL", 0)
+	// MaxRunners sets the maximum number of loaded models. MaxRunners can be configured via the OLLAMA_MAX_LOADED_MODELS environment variable.
+	MaxRunners = Uint("OLLAMA_MAX_LOADED_MODELS", 0)
+	// MaxQueue sets the maximum number of queued requests. MaxQueue can be configured via the OLLAMA_MAX_QUEUE environment variable.
+	MaxQueue = Uint("OLLAMA_MAX_QUEUE", 512)
+	// MaxVRAM sets a maximum VRAM override in bytes. MaxVRAM can be configured via the OLLAMA_MAX_VRAM environment variable.
+	MaxVRAM = Uint("OLLAMA_MAX_VRAM", 0)
 )
 
 type EnvVar struct {
@@ -274,7 +278,7 @@ func Values() map[string]string {
 	return vals
 }
 
-// getenv returns an environment variable stripped of leading and trailing quotes or spaces
-func getenv(key string) string {
-	return strings.Trim(os.Getenv(key), "\"' ")
+// Var returns an environment variable stripped of leading and trailing quotes or spaces
+func Var(key string) string {
+	return strings.Trim(strings.TrimSpace(os.Getenv(key)), "\"'")
 }
diff --git a/envconfig/config_test.go b/envconfig/config_test.go
index 977298aaf..92a500f15 100644
--- a/envconfig/config_test.go
+++ b/envconfig/config_test.go
@@ -30,6 +30,10 @@ func TestHost(t *testing.T) {
 		"extra quotes":        {"\"1.2.3.4\"", "1.2.3.4:11434"},
 		"extra space+quotes":  {" \" 1.2.3.4 \" ", "1.2.3.4:11434"},
 		"extra single quotes": {"'1.2.3.4'", "1.2.3.4:11434"},
+		"http":                {"http://1.2.3.4", "1.2.3.4:80"},
+		"http port":           {"http://1.2.3.4:4321", "1.2.3.4:4321"},
+		"https":               {"https://1.2.3.4", "1.2.3.4:443"},
+		"https port":          {"https://1.2.3.4:4321", "1.2.3.4:4321"},
 	}
 
 	for name, tt := range cases {
@@ -133,24 +137,45 @@ func TestOrigins(t *testing.T) {
 }
 
 func TestBool(t *testing.T) {
-	cases := map[string]struct {
-		value  string
-		expect bool
-	}{
-		"empty":     {"", false},
-		"true":      {"true", true},
-		"false":     {"false", false},
-		"1":         {"1", true},
-		"0":         {"0", false},
-		"random":    {"random", true},
-		"something": {"something", true},
+	cases := map[string]bool{
+		"":      false,
+		"true":  true,
+		"false": false,
+		"1":     true,
+		"0":     false,
+		// invalid values
+		"random":    true,
+		"something": true,
 	}
 
-	for name, tt := range cases {
-		t.Run(name, func(t *testing.T) {
-			t.Setenv("OLLAMA_BOOL", tt.value)
-			if b := Bool("OLLAMA_BOOL"); b() != tt.expect {
-				t.Errorf("%s: expected %t, got %t", name, tt.expect, b())
+	for k, v := range cases {
+		t.Run(k, func(t *testing.T) {
+			t.Setenv("OLLAMA_BOOL", k)
+			if b := Bool("OLLAMA_BOOL")(); b != v {
+				t.Errorf("%s: expected %t, got %t", k, v, b)
+			}
+		})
+	}
+}
+
+func TestUint(t *testing.T) {
+	cases := map[string]uint{
+		"0":    0,
+		"1":    1,
+		"1337": 1337,
+		// default values
+		"":       11434,
+		"-1":     11434,
+		"0o10":   11434,
+		"0x10":   11434,
+		"string": 11434,
+	}
+
+	for k, v := range cases {
+		t.Run(k, func(t *testing.T) {
+			t.Setenv("OLLAMA_UINT", k)
+			if i := Uint("OLLAMA_UINT", 11434)(); i != v {
+				t.Errorf("%s: expected %d, got %d", k, v, i)
 			}
 		})
 	}
@@ -188,3 +213,23 @@ func TestKeepAlive(t *testing.T) {
 		})
 	}
 }
+
+func TestVar(t *testing.T) {
+	cases := map[string]string{
+		"value":       "value",
+		" value ":     "value",
+		" 'value' ":   "value",
+		` "value" `:   "value",
+		" ' value ' ": " value ",
+		` " value " `: " value ",
+	}
+
+	for k, v := range cases {
+		t.Run(k, func(t *testing.T) {
+			t.Setenv("OLLAMA_VAR", k)
+			if s := Var("OLLAMA_VAR"); s != v {
+				t.Errorf("%s: expected %q, got %q", k, v, s)
+			}
+		})
+	}
+}
diff --git a/server/sched.go b/server/sched.go
index 610a2c50f..ce2945d8e 100644
--- a/server/sched.go
+++ b/server/sched.go
@@ -129,7 +129,7 @@ func (s *Scheduler) processPending(ctx context.Context) {
 				slog.Debug("pending request cancelled or timed out, skipping scheduling")
 				continue
 			}
-			numParallel := envconfig.NumParallel()
+			numParallel := int(envconfig.NumParallel())
 			// TODO (jmorganca): multimodal models don't support parallel yet
 			// see https://github.com/ollama/ollama/issues/4165
 			if len(pending.model.ProjectorPaths) > 0 && numParallel != 1 {
@@ -151,7 +151,7 @@ func (s *Scheduler) processPending(ctx context.Context) {
 						pending.useLoadedRunner(runner, s.finishedReqCh)
 						break
 					}
-				} else if envconfig.MaxRunners() > 0 && loadedCount >= envconfig.MaxRunners() {
+				} else if envconfig.MaxRunners() > 0 && loadedCount >= int(envconfig.MaxRunners()) {
 					slog.Debug("max runners achieved, unloading one to make room", "runner_count", loadedCount)
 					runnerToExpire = s.findRunnerToUnload()
 				} else {

From d835368eb8599b4f4c2f8a766bad5b57498a988d Mon Sep 17 00:00:00 2001
From: Jeffrey Morgan 
Date: Mon, 22 Jul 2024 16:16:22 -0400
Subject: [PATCH 135/384] convert: capture `head_dim` for mistral (#5818)

---
 convert/mistral.go | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/convert/mistral.go b/convert/mistral.go
index da6874cfd..8fe066d6a 100644
--- a/convert/mistral.go
+++ b/convert/mistral.go
@@ -71,6 +71,11 @@ func (m *MistralModel) WriteGGUF(ws io.WriteSeeker) error {
 		"tokenizer.ggml.unknown_token_id": uint32(0),
 	}
 
+	if m.Params.HeadDimension > 0 {
+		kv["llama.attention.key_length"] = uint32(m.Params.HeadDimension)
+		kv["llama.attention.value_length"] = uint32(m.Params.HeadDimension)
+	}
+
 	return llm.NewGGUFV3(m.Params.ByteOrder).Encode(ws, kv, m.Tensors)
 }
 

From c0648233f2236f82f6830d2aaed552ae0f72379b Mon Sep 17 00:00:00 2001
From: royjhan <65097070+royjhan@users.noreply.github.com>
Date: Mon, 22 Jul 2024 13:37:08 -0700
Subject: [PATCH 136/384] api embed docs (#5282)

---
 docs/api.md | 84 ++++++++++++++++++++++++++++++++++++++++++++++++-----
 1 file changed, 76 insertions(+), 8 deletions(-)

diff --git a/docs/api.md b/docs/api.md
index c577bb1a5..4381c3768 100644
--- a/docs/api.md
+++ b/docs/api.md
@@ -1026,7 +1026,7 @@ If `stream` is set to `false`, then the response is a single JSON object:
 ## Generate Embeddings
 
 ```shell
-POST /api/embeddings
+POST /api/embed
 ```
 
 Generate embeddings from a model
@@ -1034,10 +1034,11 @@ Generate embeddings from a model
 ### Parameters
 
 - `model`: name of model to generate embeddings from
-- `prompt`: text to generate embeddings for
+- `input`: text or list of text to generate embeddings for
 
 Advanced parameters:
 
+- `truncate`: truncates the end of each input to fit within context length. Returns error if `false` and context length is exceeded. Defaults to `true`
 - `options`: additional model parameters listed in the documentation for the [Modelfile](./modelfile.md#valid-parameters-and-values) such as `temperature`
 - `keep_alive`: controls how long the model will stay loaded into memory following the request (default: `5m`)
 
@@ -1046,9 +1047,9 @@ Advanced parameters:
 #### Request
 
 ```shell
-curl http://localhost:11434/api/embeddings -d '{
+curl http://localhost:11434/api/embed -d '{
   "model": "all-minilm",
-  "prompt": "Here is an article about llamas..."
+  "input": "Why is the sky blue?"
 }'
 ```
 
@@ -1056,10 +1057,35 @@ curl http://localhost:11434/api/embeddings -d '{
 
 ```json
 {
-  "embedding": [
-    0.5670403838157654, 0.009260174818336964, 0.23178744316101074, -0.2916173040866852, -0.8924556970596313,
-    0.8785552978515625, -0.34576427936553955, 0.5742510557174683, -0.04222835972905159, -0.137906014919281
-  ]
+  "model": "all-minilm",
+  "embeddings": [[
+    0.010071029, -0.0017594862, 0.05007221, 0.04692972, 0.054916814,
+    0.008599704, 0.105441414, -0.025878139, 0.12958129, 0.031952348
+  ]]
+}
+```
+
+#### Request (Multiple input)
+
+```shell
+curl http://localhost:11434/api/embed -d '{
+  "model": "all-minilm",
+  "input": ["Why is the sky blue?", "Why is the grass green?"]
+}'
+```
+
+#### Response
+
+```json
+{
+  "model": "all-minilm",
+  "embeddings": [[
+    0.010071029, -0.0017594862, 0.05007221, 0.04692972, 0.054916814,
+    0.008599704, 0.105441414, -0.025878139, 0.12958129, 0.031952348
+  ],[
+    -0.0098027075, 0.06042469, 0.025257962, -0.006364387, 0.07272725,
+    0.017194884, 0.09032035, -0.051705178, 0.09951512, 0.09072481
+  ]]
 }
 ```
 
@@ -1106,3 +1132,45 @@ A single JSON object will be returned.
   ]
 }
 ```
+
+## Generate Embedding
+
+> Note: this endpoint has been superseded by `/api/embed`
+
+```shell
+POST /api/embeddings
+```
+
+Generate embeddings from a model
+
+### Parameters
+
+- `model`: name of model to generate embeddings from
+- `prompt`: text to generate embeddings for
+
+Advanced parameters:
+
+- `options`: additional model parameters listed in the documentation for the [Modelfile](./modelfile.md#valid-parameters-and-values) such as `temperature`
+- `keep_alive`: controls how long the model will stay loaded into memory following the request (default: `5m`)
+
+### Examples
+
+#### Request
+
+```shell
+curl http://localhost:11434/api/embeddings -d '{
+  "model": "all-minilm",
+  "prompt": "Here is an article about llamas..."
+}'
+```
+
+#### Response
+
+```json
+{
+  "embedding": [
+    0.5670403838157654, 0.009260174818336964, 0.23178744316101074, -0.2916173040866852, -0.8924556970596313,
+    0.8785552978515625, -0.34576427936553955, 0.5742510557174683, -0.04222835972905159, -0.137906014919281
+  ]
+}
+```
\ No newline at end of file

From 83a0cb8d88561b4302baa8b6ea0623c426483e5d Mon Sep 17 00:00:00 2001
From: Michael Yang 
Date: Tue, 2 Jul 2024 14:52:18 -0700
Subject: [PATCH 137/384] docs

---
 docs/template.md | 173 +++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 173 insertions(+)
 create mode 100644 docs/template.md

diff --git a/docs/template.md b/docs/template.md
new file mode 100644
index 000000000..8f41e8fb6
--- /dev/null
+++ b/docs/template.md
@@ -0,0 +1,173 @@
+# Template
+
+Ollama provides a powerful templating engine backed by Go's built-in templating engine to construct prompts for your large language model. This feature is a valuable tool to get the most out of your models.
+
+## Basic Template Structure
+
+A basic Go template consists of three main parts:
+
+* **Layout**: The overall structure of the template.
+* **Variables**: Placeholders for dynamic data that will be replaced with actual values when the template is rendered.
+* **Functions**: Custom functions or logic that can be used to manipulate the template's content.
+
+Here's an example of a simple chat template:
+
+```gotmpl
+{{- range .Messages }}
+{{ .Role }}: {{ .Content }}
+{{- end }}
+```
+
+In this example, we have:
+
+* A basic messages structure (layout)
+* Three variables: `Messages`, `Role`, and `Content` (variables)
+* A custom function (action) that iterates over an array of items (`range .Messages`) and displays each item
+
+## Adding Templates to Your Model
+
+By default, models imported into Ollama have a default template of `{{ .Prompt }}`, i.e. user inputs are sent verbatim to the LLM. This is appropriate for text or code completion models but lacks essential markers for chat or instruction models.
+
+Omitting a template in these models puts the responsibility of correctly templating input onto the user. Adding a template allows users to easily get the best results from the model.
+
+To add templates in your model, you'll need to add a `TEMPLATE` command to the Modelfile. Here's an example using Meta's Llama 3.
+
+```dockerfile
+FROM llama3
+
+TEMPLATE """{{- if .System }}<|start_header_id|>system<|end_header_id|>
+
+{{ .System }}<|eot_id|>
+{{- end }}
+{{- range .Messages }}<|start_header_id|>{{ .Role }}<|end_header_id|>
+
+{{ .Content }}<|eot_id|>
+{{- end }}<|start_header_id|>assistant<|end_header_id|>
+
+"""
+```
+
+## Variables
+
+`System` (string): system prompt
+
+`Prompt` (string): user prompt
+
+`Response` (string): assistant response
+
+`Suffix` (string): text inserted after the assistant's response
+
+`Messages` (list): list of messages
+
+`Messages[].Role` (string): role which can be one of `system`, `user`, `assistant`, or `tool`
+
+`Messages[].Content` (string):  message content
+
+`Messages[].ToolCalls` (list): list of tools the model wants to call
+
+`Messages[].ToolCalls[].Function` (object): function to call
+
+`Messages[].ToolCalls[].Function.Name` (string): function name
+
+`Messages[].ToolCalls[].Function.Arguments` (map): mapping of argument name to argument value
+
+`Tools` (list): list of tools the model can access
+
+`Tools[].Type` (string): schema type. `type` is always `function`
+
+`Tools[].Function` (object): function definition
+
+`Tools[].Function.Name` (string): function name
+
+`Tools[].Function.Description` (string): function description
+
+`Tools[].Function.Parameters` (object): function parameters
+
+`Tools[].Function.Parameters.Type` (string): schema type. `type` is always `object`
+
+`Tools[].Function.Parameters.Required` (list): list of required properties
+
+`Tools[].Function.Parameters.Properties` (map): mapping of property name to property definition
+
+`Tools[].Function.Parameters.Properties[].Type` (string): property type
+
+`Tools[].Function.Parameters.Properties[].Description` (string): property description
+
+`Tools[].Function.Parameters.Properties[].Enum` (list): list of valid values
+
+## Tips and Best Practices
+
+Keep the following tips and best practices in mind when working with Go templates:
+
+* **Be mindful of dot**: Control flow structures like `range` and `with` changes the value `.`
+* **Out-of-scope variables**: Use `$.` to reference variables not currently in scope, starting from the root
+* **Whitespace control**: Use `-` to trim leading (`{{-`) and trailing (`-}}`) whitespace
+
+## Examples
+
+### Example Messages
+
+#### ChatML
+
+ChatML is a popular template format. It can be used for models such as Databrick's DBRX, Intel's Neural Chat, and Microsoft's Orca 2.
+
+```gotmpl
+{{- if .System }}<|im_start|>system
+{{ .System }}<|im_end|>
+{{ end }}
+{{- range .Messages }}<|im_start|>{{ .Role }}
+{{ .Content }}<|im_end|>
+{{ end }}<|im_start|>assistant
+{{ else }}
+{{ if .System }}<|im_start|>system
+{{ .System }}<|im_end|>
+```
+
+### Example Tools
+
+Tools support can be added to a model by adding a `{{ .Tools }}` node to the template. This feature is useful for models trained to call external tools and can a powerful tool for retrieving real-time data or performing complex tasks.
+
+#### Mistral
+
+Mistral v0.3 and Mixtral 8x22B supports tool calling.
+
+```gotmpl
+{{- range $index, $_ := .Messages }}
+{{- if eq .Role "user" }}
+{{- if and (le (len (slice $.Messages $index)) 2) $.Tools }}[AVAILABLE_TOOLS] {{ json $.Tools }}[/AVAILABLE_TOOLS]
+{{- end }}[INST] {{ if and (eq (len (slice $.Messages $index)) 1) $.System }}{{ $.System }}
+
+{{ end }}{{ .Content }}[/INST]
+{{- else if eq .Role "assistant" }}
+{{- if .Content }} {{ .Content }}
+{{- else if .ToolCalls }}[TOOL_CALLS] [
+{{- range .ToolCalls }}{"name": "{{ .Function.Name }}", "arguments": {{ json .Function.Arguments }}}
+{{- end }}]
+{{- end }}
+{{- else if eq .Role "tool" }}[TOOL_RESULTS] {"content": {{ .Content }}}[/TOOL_RESULTS]
+{{- end }}
+{{- end }}
+```
+
+### Example Fill-in-Middle
+
+Fill-in-middle support can be added to a model by adding a `{{ .Suffix }}` node to the template. This feature is useful for models that are trained to generate text in the middle of user input, such as code completion models.
+
+#### CodeLlama
+
+CodeLlama [7B](https://ollama.com/library/codellama:7b-code) and [13B](https://ollama.com/library/codellama:13b-code) code completion models support fill-in-middle.
+
+```gotmpl
+
 {{ .Prompt }} {{ .Suffix }} 
+```
+
+> [!NOTE]
+> CodeLlama 34B and 70B code completion and all instruct and Python fine-tuned models do not support fill-in-middle.
+
+#### Codestral
+
+Codestral [22B](https://ollama.com/library/codestral:22b) supports fill-in-middle.
+
+```gotmpl
+[SUFFIX]{{ .Suffix }}[PREFIX] {{ .Prompt }}
+```

From 9b60a038e5169c4a69bc513ae6e7ea1816f9fc11 Mon Sep 17 00:00:00 2001
From: Michael Yang 
Date: Mon, 22 Jul 2024 13:34:56 -0700
Subject: [PATCH 138/384] update api.md

---
 README.md         |   3 +-
 docs/api.md       | 117 +++++++++++++++++++++++++++++++++++++++++++++-
 docs/modelfile.md |   3 +-
 3 files changed, 119 insertions(+), 4 deletions(-)

diff --git a/README.md b/README.md
index b96f4c161..02ab70514 100644
--- a/README.md
+++ b/README.md
@@ -64,7 +64,8 @@ Here are some example models that can be downloaded:
 | LLaVA              | 7B         | 4.5GB | `ollama run llava`             |
 | Solar              | 10.7B      | 6.1GB | `ollama run solar`             |
 
-> Note: You should have at least 8 GB of RAM available to run the 7B models, 16 GB to run the 13B models, and 32 GB to run the 33B models.
+> [!NOTE]
+> You should have at least 8 GB of RAM available to run the 7B models, 16 GB to run the 13B models, and 32 GB to run the 33B models.
 
 ## Customize a model
 
diff --git a/docs/api.md b/docs/api.md
index c577bb1a5..bf4c8ce8e 100644
--- a/docs/api.md
+++ b/docs/api.md
@@ -40,6 +40,7 @@ Generate a response for a given prompt with a provided model. This is a streamin
 
 - `model`: (required) the [model name](#model-names)
 - `prompt`: the prompt to generate a response for
+- `suffix`: the text after the model response
 - `images`: (optional) a list of base64-encoded images (for multimodal models such as `llava`)
 
 Advanced parameters (optional):
@@ -57,7 +58,8 @@ Advanced parameters (optional):
 
 Enable JSON mode by setting the `format` parameter to `json`. This will structure the response as a valid JSON object. See the JSON mode [example](#request-json-mode) below.
 
-> Note: it's important to instruct the model to use JSON in the `prompt`. Otherwise, the model may generate large amounts whitespace.
+> [!IMPORTANT]
+> It's important to instruct the model to use JSON in the `prompt`. Otherwise, the model may generate large amounts whitespace.
 
 ### Examples
 
@@ -148,8 +150,44 @@ If `stream` is set to `false`, the response will be a single JSON object:
 }
 ```
 
+#### Request (with suffix)
+
+##### Request
+
+```shell
+curl http://localhost:11434/api/generate -d '{
+  "model": "codellama:code",
+  "prompt": "def compute_gcd(a, b):",
+  "suffix": "    return result",
+  "options": {
+    "temperature": 0
+  },
+  "stream": false
+}'
+```
+
+##### Response
+
+```json
+{
+  "model": "codellama:code",
+  "created_at": "2024-07-22T20:47:51.147561Z",
+  "response": "\n  if a == 0:\n    return b\n  else:\n    return compute_gcd(b % a, a)\n\ndef compute_lcm(a, b):\n  result = (a * b) / compute_gcd(a, b)\n",
+  "done": true,
+  "done_reason": "stop",
+  "context": [...],
+  "total_duration": 1162761250,
+  "load_duration": 6683708,
+  "prompt_eval_count": 17,
+  "prompt_eval_duration": 201222000,
+  "eval_count": 63,
+  "eval_duration": 953997000
+}
+```
+
 #### Request (JSON mode)
 
+> [!IMPORTANT]
 > When `format` is set to `json`, the output will always be a well-formed JSON object. It's important to also instruct the model to respond in JSON.
 
 ##### Request
@@ -383,9 +421,10 @@ Generate the next message in a chat with a provided model. This is a streaming e
 
 The `message` object has the following fields:
 
-- `role`: the role of the message, either `system`, `user` or `assistant`
+- `role`: the role of the message, either `system`, `user`, `assistant`, or `tool`
 - `content`: the content of the message
 - `images` (optional): a list of images to include in the message (for multimodal models such as `llava`)
+- `tool_calls` (optional): a list of tools the model wants to use
 
 Advanced parameters (optional):
 
@@ -393,6 +432,7 @@ Advanced parameters (optional):
 - `options`: additional model parameters listed in the documentation for the [Modelfile](./modelfile.md#valid-parameters-and-values) such as `temperature`
 - `stream`: if `false` the response will be returned as a single response object, rather than a stream of objects
 - `keep_alive`: controls how long the model will stay loaded into memory following the request (default: `5m`)
+- `tools`: external tools the model can use. Not all models support this feature.
 
 ### Examples
 
@@ -622,6 +662,79 @@ curl http://localhost:11434/api/chat -d '{
 }
 ```
 
+#### Chat request (with tools)
+
+##### Request
+
+```
+curl http://localhost:11434/api/chat -d '{
+  "model": "mistral",
+  "messages": [
+    {
+      "role": "user",
+      "content": "What is the weather today in Paris?"
+    }
+  ],
+  "stream": false,
+  "tools": [
+    {
+      "type": "function",
+      "function": {
+        "name": "get_current_weather",
+        "description": "Get the current weather for a location",
+        "parameters": {
+          "type": "object",
+          "properties": {
+            "location": {
+              "type": "string",
+              "description": "The location to get the weather for, e.g. San Francisco, CA"
+            },
+            "format": {
+              "type": "string",
+              "description": "The format to return the weather in, e.g. 'celsius' or 'fahrenheit'",
+              "enum": ["celsius", "fahrenheit"]
+            }
+          },
+          "required": ["location", "format"]
+        }
+      }
+    }
+  ]
+}'
+```
+
+##### Response
+
+```json
+{
+  "model": "mistral:7b-instruct-v0.3-q4_K_M",
+  "created_at": "2024-07-22T20:33:28.123648Z",
+  "message": {
+    "role": "assistant",
+    "content": "",
+    "tool_calls": [
+      {
+        "function": {
+          "name": "get_current_weather",
+          "arguments": {
+            "format": "celsius",
+            "location": "Paris, FR"
+          }
+        }
+      }
+    ]
+  },
+  "done_reason": "stop",
+  "done": true,
+  "total_duration": 885095291,
+  "load_duration": 3753500,
+  "prompt_eval_count": 122,
+  "prompt_eval_duration": 328493000,
+  "eval_count": 33,
+  "eval_duration": 552222000
+}
+```
+
 ## Create a Model
 
 ```shell
diff --git a/docs/modelfile.md b/docs/modelfile.md
index 21ee1826e..c3645b062 100644
--- a/docs/modelfile.md
+++ b/docs/modelfile.md
@@ -1,6 +1,7 @@
 # Ollama Model File
 
-> Note: `Modelfile` syntax is in development
+> [!NOTE]
+> `Modelfile` syntax is in development
 
 A model file is the blueprint to create and share models with Ollama.
 

From e12fff8810e37bfabe4416f7f41902387ff3aae1 Mon Sep 17 00:00:00 2001
From: Daniel Hiltgen 
Date: Mon, 15 Jul 2024 09:25:56 -0700
Subject: [PATCH 139/384] Enable windows error dialog for subprocess startup

Make sure if something goes wrong spawning the process, the user gets
enough info to be able to try to self correct, or at least file a bug
with details so we can fix it.  Once the process starts, we immediately
change back to the recommended setting to prevent the blocking dialog.
This ensures if the model fails to load (OOM, unsupported model type,
etc.) the process will exit quickly and we can scan the stdout/stderr
of the subprocess for the reason to report via API.
---
 llm/ext_server/server.cpp |  4 ++++
 llm/llm_darwin_amd64.go   |  3 +++
 llm/llm_darwin_arm64.go   |  3 +++
 llm/llm_linux.go          |  7 ++++++-
 llm/llm_windows.go        | 16 +++++++++++++++-
 llm/server.go             |  1 +
 6 files changed, 32 insertions(+), 2 deletions(-)

diff --git a/llm/ext_server/server.cpp b/llm/ext_server/server.cpp
index e8a076c43..14d921c04 100644
--- a/llm/ext_server/server.cpp
+++ b/llm/ext_server/server.cpp
@@ -41,6 +41,7 @@
 
 #if defined(_WIN32)
 #include 
+#include 
 #endif
 
 #include 
@@ -2737,6 +2738,9 @@ int wmain(int argc, wchar_t **wargv) {
     for (int i = 0; i < argc; ++i) {
         argv[i] = wchar_to_char(wargv[i]);
     }
+
+    // Adjust error mode to avoid error dialog after we start.
+    SetErrorMode(SEM_FAILCRITICALERRORS);
 #else
 int main(int argc, char **argv) {
 #endif
diff --git a/llm/llm_darwin_amd64.go b/llm/llm_darwin_amd64.go
index 3093e1ad2..60eed7194 100644
--- a/llm/llm_darwin_amd64.go
+++ b/llm/llm_darwin_amd64.go
@@ -2,7 +2,10 @@ package llm
 
 import (
 	"embed"
+	"syscall"
 )
 
 //go:embed build/darwin/x86_64/*/bin/*
 var libEmbed embed.FS
+
+var LlamaServerSysProcAttr = &syscall.SysProcAttr{}
diff --git a/llm/llm_darwin_arm64.go b/llm/llm_darwin_arm64.go
index 928f0b824..20ce8552e 100644
--- a/llm/llm_darwin_arm64.go
+++ b/llm/llm_darwin_arm64.go
@@ -2,7 +2,10 @@ package llm
 
 import (
 	"embed"
+	"syscall"
 )
 
 //go:embed build/darwin/arm64/*/bin/*
 var libEmbed embed.FS
+
+var LlamaServerSysProcAttr = &syscall.SysProcAttr{}
diff --git a/llm/llm_linux.go b/llm/llm_linux.go
index c2c5c4cb9..928b4e791 100644
--- a/llm/llm_linux.go
+++ b/llm/llm_linux.go
@@ -1,6 +1,11 @@
 package llm
 
-import "embed"
+import (
+	"embed"
+	"syscall"
+)
 
 //go:embed build/linux/*/*/bin/*
 var libEmbed embed.FS
+
+var LlamaServerSysProcAttr = &syscall.SysProcAttr{}
diff --git a/llm/llm_windows.go b/llm/llm_windows.go
index e44f4b951..763cccf9c 100644
--- a/llm/llm_windows.go
+++ b/llm/llm_windows.go
@@ -1,6 +1,20 @@
 package llm
 
-import "embed"
+import (
+	"embed"
+	"syscall"
+)
 
 // unused on windows
 var libEmbed embed.FS
+
+const CREATE_DEFAULT_ERROR_MODE = 0x04000000
+
+var LlamaServerSysProcAttr = &syscall.SysProcAttr{
+	// Wire up the default error handling logic If for some reason a DLL is
+	// missing in the path this will pop up a GUI Dialog explaining the fault so
+	// the user can either fix their PATH, or report a bug. Without this
+	// setting, the process exits immediately with a generic exit status but no
+	// way to (easily) figure out what the actual missing DLL was.
+	CreationFlags: CREATE_DEFAULT_ERROR_MODE,
+}
diff --git a/llm/server.go b/llm/server.go
index 08463ef01..557327732 100644
--- a/llm/server.go
+++ b/llm/server.go
@@ -346,6 +346,7 @@ func NewLlamaServer(gpus gpu.GpuInfoList, model string, ggml *GGML, adapters, pr
 		s.cmd.Env = os.Environ()
 		s.cmd.Stdout = os.Stdout
 		s.cmd.Stderr = s.status
+		s.cmd.SysProcAttr = LlamaServerSysProcAttr
 
 		envWorkarounds := [][2]string{}
 		for _, gpu := range gpus {

From db0968f30c895b9f2059da48800018739ef9bca7 Mon Sep 17 00:00:00 2001
From: Josh <76125168+joshyan1@users.noreply.github.com>
Date: Mon, 22 Jul 2024 15:48:15 -0700
Subject: [PATCH 140/384] fix dupe err message (#5857)

---
 server/routes.go | 7 +++----
 1 file changed, 3 insertions(+), 4 deletions(-)

diff --git a/server/routes.go b/server/routes.go
index 0d7ca003c..e6ffe5268 100644
--- a/server/routes.go
+++ b/server/routes.go
@@ -609,10 +609,9 @@ func (s *Server) CreateModelHandler(c *gin.Context) {
 		defer cancel()
 
 		quantization := cmp.Or(r.Quantize, r.Quantization)
-		if err := CreateModel(ctx, name, filepath.Dir(r.Path), strings.ToUpper(quantization), f, fn); err != nil {
-			if errors.Is(err, errBadTemplate) {
-				ch <- gin.H{"error": err.Error(), "status": http.StatusBadRequest}
-			}
+		if err := CreateModel(ctx, name, filepath.Dir(r.Path), strings.ToUpper(quantization), f, fn); errors.Is(err, errBadTemplate) {
+			ch <- gin.H{"error": err.Error(), "status": http.StatusBadRequest}
+		} else if err != nil {
 			ch <- gin.H{"error": err.Error()}
 		}
 	}()

From 5d604eec5bbaba840fcee8cac8574807f3656ea8 Mon Sep 17 00:00:00 2001
From: Daniel Hiltgen 
Date: Mon, 22 Jul 2024 16:16:28 -0700
Subject: [PATCH 141/384] Bump Go patch version

---
 .github/workflows/release.yaml | 10 +++++-----
 .github/workflows/test.yaml    | 10 +++++-----
 Dockerfile                     |  2 +-
 3 files changed, 11 insertions(+), 11 deletions(-)

diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml
index 5ae630c31..f0c6db5dd 100644
--- a/.github/workflows/release.yaml
+++ b/.github/workflows/release.yaml
@@ -31,7 +31,7 @@ jobs:
           security set-keychain-settings -lut 3600 build.keychain
       - uses: actions/setup-go@v5
         with:
-          go-version-file: go.mod
+          go-version: "stable"
           cache: true
       - name: Build Darwin
         env:
@@ -87,7 +87,7 @@ jobs:
           write-host "plugin installed"
       - uses: actions/setup-go@v5
         with:
-          go-version-file: go.mod
+          go-version: "stable"
           cache: true
       - run: go get ./...
       - run: |
@@ -141,7 +141,7 @@ jobs:
           write-host "plugin installed"
       - uses: actions/setup-go@v5
         with:
-          go-version-file: go.mod
+          go-version: "stable"
           cache: true
       - name: 'Install ROCm'
         run: |
@@ -218,7 +218,7 @@ jobs:
           write-host "plugin installed"
       - uses: actions/setup-go@v5
         with:
-          go-version-file: go.mod
+          go-version: "stable"
           cache: true
       - name: 'Install CUDA'
         run: |
@@ -306,7 +306,7 @@ jobs:
           write-host "plugin installed"
       - uses: actions/setup-go@v5
         with:
-          go-version-file: go.mod
+          go-version: "stable"
           cache: true
       - run: go get
       - uses: actions/download-artifact@v4
diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml
index 90fef6e59..5e002a225 100644
--- a/.github/workflows/test.yaml
+++ b/.github/workflows/test.yaml
@@ -63,7 +63,7 @@ jobs:
       - uses: actions/checkout@v4
       - uses: actions/setup-go@v5
         with:
-          go-version-file: go.mod
+          go-version: "stable"
           cache: true
       - run: go get ./...
       - run: |
@@ -163,7 +163,7 @@ jobs:
       - uses: actions/checkout@v4
       - uses: actions/setup-go@v5
         with:
-          go-version-file: go.mod
+          go-version: "stable"
           cache: true
       - name: 'Install ROCm'
         run: |
@@ -200,7 +200,7 @@ jobs:
       - uses: actions/checkout@v4
       - uses: actions/setup-go@v5
         with:
-          go-version-file: go.mod
+          go-version: "stable"
           cache: true
       - name: 'Install CUDA'
         run: |
@@ -255,7 +255,7 @@ jobs:
           submodules: recursive
       - uses: actions/setup-go@v5
         with:
-          go-version-file: go.mod
+          go-version: "stable"
           cache: false
       - run: |
           case ${{ matrix.arch }} in
@@ -297,7 +297,7 @@ jobs:
           submodules: recursive
       - uses: actions/setup-go@v5
         with:
-          go-version-file: go.mod
+          go-version: "stable"
           cache: true
       - run: |
           case ${{ matrix.arch }} in
diff --git a/Dockerfile b/Dockerfile
index ca3934964..c8efdd8a2 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,4 +1,4 @@
-ARG GOLANG_VERSION=1.22.1
+ARG GOLANG_VERSION=1.22.5
 ARG CMAKE_VERSION=3.22.1
 # this CUDA_VERSION corresponds with the one specified in docs/gpu.md
 ARG CUDA_VERSION=11.3.1

From a6cd8f6169c029c92105962017562274bd90626b Mon Sep 17 00:00:00 2001
From: Ajay Chintala 
Date: Tue, 23 Jul 2024 11:40:23 -0700
Subject: [PATCH 142/384] Update README.md to add LLMStack integration (#5799)

---
 README.md | 1 +
 1 file changed, 1 insertion(+)

diff --git a/README.md b/README.md
index b96f4c161..6a06b8191 100644
--- a/README.md
+++ b/README.md
@@ -296,6 +296,7 @@ See the [API documentation](./docs/api.md) for all endpoints.
 - [Kerlig AI](https://www.kerlig.com/) (AI writing assistant for macOS)
 - [AI Studio](https://github.com/MindWorkAI/AI-Studio)
 - [Sidellama](https://github.com/gyopak/sidellama) (browser-based LLM client)
+- [LLMStack](https://github.com/trypromptly/LLMStack) (No-code multi-agent framework to build LLM agents and workflows)
 
 ### Terminal
 

From 830fdd271536ee257db72c29c2be5b5629e58389 Mon Sep 17 00:00:00 2001
From: Daniel Hiltgen 
Date: Tue, 23 Jul 2024 15:14:28 -0700
Subject: [PATCH 143/384] Better explain multi-gpu behavior

---
 cmd/cmd.go  | 1 +
 docs/faq.md | 6 +++++-
 2 files changed, 6 insertions(+), 1 deletion(-)

diff --git a/cmd/cmd.go b/cmd/cmd.go
index b761d018f..610fddcb0 100644
--- a/cmd/cmd.go
+++ b/cmd/cmd.go
@@ -1341,6 +1341,7 @@ func NewCLI() *cobra.Command {
 				envVars["OLLAMA_NUM_PARALLEL"],
 				envVars["OLLAMA_NOPRUNE"],
 				envVars["OLLAMA_ORIGINS"],
+				envVars["OLLAMA_SCHED_SPREAD"],
 				envVars["OLLAMA_TMPDIR"],
 				envVars["OLLAMA_FLASH_ATTENTION"],
 				envVars["OLLAMA_LLM_LIBRARY"],
diff --git a/docs/faq.md b/docs/faq.md
index da1848f7f..16c805497 100644
--- a/docs/faq.md
+++ b/docs/faq.md
@@ -272,4 +272,8 @@ The following server settings may be used to adjust how Ollama handles concurren
 - `OLLAMA_NUM_PARALLEL` - The maximum number of parallel requests each model will process at the same time.  The default will auto-select either 4 or 1 based on available memory.
 - `OLLAMA_MAX_QUEUE` - The maximum number of requests Ollama will queue when busy before rejecting additional requests. The default is 512
 
-Note: Windows with Radeon GPUs currently default to 1 model maximum due to limitations in ROCm v5.7 for available VRAM reporting.  Once ROCm v6.2 is available, Windows Radeon will follow the defaults above.  You may enable concurrent model loads on Radeon on Windows, but ensure you don't load more models than will fit into your GPUs VRAM.
\ No newline at end of file
+Note: Windows with Radeon GPUs currently default to 1 model maximum due to limitations in ROCm v5.7 for available VRAM reporting.  Once ROCm v6.2 is available, Windows Radeon will follow the defaults above.  You may enable concurrent model loads on Radeon on Windows, but ensure you don't load more models than will fit into your GPUs VRAM.
+
+## How does Ollama load models on multiple GPUs?
+
+Installing multiple GPUs of the same brand can be a great way to increase your available VRAM to load larger models.  When you load a new model, Ollama evaluates the required VRAM for the model against what is currently available.  If the model will entirely fit on any single GPU, Ollama will load the model on that GPU.  This typically provides the best performance as it reduces the amount of data transfering across the PCI bus during inference.  If the model does not fit entirely on one GPU, then it will be spread across all the available GPUs.
\ No newline at end of file

From ac33aa7d3782887878e6e24fb4a6238356a489a6 Mon Sep 17 00:00:00 2001
From: royjhan <65097070+royjhan@users.noreply.github.com>
Date: Wed, 24 Jul 2024 11:15:46 -0700
Subject: [PATCH 144/384] Fix Embed Test Flakes (#5893)

* float cmp

* increase tolerance
---
 integration/embed_test.go | 59 +++++++++++++++++++++++++++++++++++----
 1 file changed, 54 insertions(+), 5 deletions(-)

diff --git a/integration/embed_test.go b/integration/embed_test.go
index aeafa57b6..61b36fa29 100644
--- a/integration/embed_test.go
+++ b/integration/embed_test.go
@@ -4,12 +4,45 @@ package integration
 
 import (
 	"context"
+	"math"
 	"testing"
 	"time"
 
 	"github.com/ollama/ollama/api"
 )
 
+func floatsEqual32(a, b float32) bool {
+	return math.Abs(float64(a-b)) <= 1e-4
+}
+
+func floatsEqual64(a, b float64) bool {
+	return math.Abs(a-b) <= 1e-4
+}
+
+func TestAllMiniLMEmbeddings(t *testing.T) {
+	ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute)
+	defer cancel()
+
+	req := api.EmbeddingRequest{
+		Model:  "all-minilm",
+		Prompt: "why is the sky blue?",
+	}
+
+	res, err := embeddingTestHelper(ctx, t, req)
+
+	if err != nil {
+		t.Fatalf("error: %v", err)
+	}
+
+	if len(res.Embedding) != 384 {
+		t.Fatalf("expected 384 floats, got %d", len(res.Embedding))
+	}
+
+	if !floatsEqual64(res.Embedding[0], 0.06642947345972061) {
+		t.Fatalf("expected 0.06642947345972061, got %.16f", res.Embedding[0])
+	}
+}
+
 func TestAllMiniLMEmbed(t *testing.T) {
 	ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute)
 	defer cancel()
@@ -33,8 +66,8 @@ func TestAllMiniLMEmbed(t *testing.T) {
 		t.Fatalf("expected 384 floats, got %d", len(res.Embeddings[0]))
 	}
 
-	if res.Embeddings[0][0] != 0.010071031 {
-		t.Fatalf("expected 0.010071031, got %f", res.Embeddings[0][0])
+	if !floatsEqual32(res.Embeddings[0][0], 0.010071031) {
+		t.Fatalf("expected 0.010071031, got %.8f", res.Embeddings[0][0])
 	}
 }
 
@@ -61,12 +94,12 @@ func TestAllMiniLMBatchEmbed(t *testing.T) {
 		t.Fatalf("expected 384 floats, got %d", len(res.Embeddings[0]))
 	}
 
-	if res.Embeddings[0][0] != 0.010071031 || res.Embeddings[1][0] != -0.009802706 {
-		t.Fatalf("expected 0.010071031 and -0.009802706, got %f and %f", res.Embeddings[0][0], res.Embeddings[1][0])
+	if !floatsEqual32(res.Embeddings[0][0], 0.010071031) || !floatsEqual32(res.Embeddings[1][0], -0.009802706) {
+		t.Fatalf("expected 0.010071031 and -0.009802706, got %.8f and %.8f", res.Embeddings[0][0], res.Embeddings[1][0])
 	}
 }
 
-func TestAllMiniLmEmbedTruncate(t *testing.T) {
+func TestAllMiniLMEmbedTruncate(t *testing.T) {
 	ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute)
 	defer cancel()
 
@@ -135,6 +168,22 @@ func TestAllMiniLmEmbedTruncate(t *testing.T) {
 	}
 }
 
+func embeddingTestHelper(ctx context.Context, t *testing.T, req api.EmbeddingRequest) (*api.EmbeddingResponse, error) {
+	client, _, cleanup := InitServerConnection(ctx, t)
+	defer cleanup()
+	if err := PullIfMissing(ctx, client, req.Model); err != nil {
+		t.Fatalf("failed to pull model %s: %v", req.Model, err)
+	}
+
+	response, err := client.Embeddings(ctx, &req)
+
+	if err != nil {
+		return nil, err
+	}
+
+	return response, nil
+}
+
 func embedTestHelper(ctx context.Context, t *testing.T, req api.EmbedRequest) (*api.EmbedResponse, error) {
 	client, _, cleanup := InitServerConnection(ctx, t)
 	defer cleanup()

From bb46bbcf5e90e5efab5ff946a6c798131907ba2d Mon Sep 17 00:00:00 2001
From: Michael Yang 
Date: Wed, 24 Jul 2024 13:05:59 -0700
Subject: [PATCH 145/384] llm(llama): pass rope factors (#5924)

---
 llm/patches/0001-llama-3.1-rope-scaling.diff | 71 ++++++++++++++++++++
 1 file changed, 71 insertions(+)
 create mode 100644 llm/patches/0001-llama-3.1-rope-scaling.diff

diff --git a/llm/patches/0001-llama-3.1-rope-scaling.diff b/llm/patches/0001-llama-3.1-rope-scaling.diff
new file mode 100644
index 000000000..45dcb4f50
--- /dev/null
+++ b/llm/patches/0001-llama-3.1-rope-scaling.diff
@@ -0,0 +1,71 @@
+From 2f872f294fb6f5c6e8f983b68c40ea656053dd92 Mon Sep 17 00:00:00 2001
+From: Michael Yang 
+Date: Tue, 23 Jul 2024 14:33:29 -0700
+Subject: [PATCH] llama 3.1 rope scaling
+
+---
+ src/llama.cpp | 14 ++++++++++++--
+ 1 file changed, 12 insertions(+), 2 deletions(-)
+
+diff --git a/src/llama.cpp b/src/llama.cpp
+index 8fe51971..a9969df8 100644
+--- a/src/llama.cpp
++++ b/src/llama.cpp
+@@ -2472,6 +2472,7 @@ struct llama_layer {
+     // long rope factors
+     struct ggml_tensor * rope_long  = nullptr;
+     struct ggml_tensor * rope_short = nullptr;
++    struct ggml_tensor * rope_freqs = nullptr;
+ 
+     // bitnet scale
+     struct ggml_tensor * wq_scale;
+@@ -6143,6 +6144,8 @@ static bool llm_load_tensors(
+ 
+                         layer.ffn_norm = ml.create_tensor(ctx_layer, tn(LLM_TENSOR_FFN_NORM, "weight", i), {n_embd});
+ 
++                        layer.rope_freqs  = ml.create_tensor(ctx_layer, tn(LLM_TENSOR_ROPE_FREQS,  "weight"), { n_embd/n_head/2 }, llama_model_loader::TENSOR_NOT_REQUIRED | (i != 0 ? llama_model_loader::TENSOR_DUPLICATED : 0));
++
+                         if (n_expert == 0) {
+                             layer.ffn_gate = ml.create_tensor(ctx_split, tn(LLM_TENSOR_FFN_GATE, "weight", i), {n_embd,   n_ff});
+                             layer.ffn_down = ml.create_tensor(ctx_split, tn(LLM_TENSOR_FFN_DOWN, "weight", i), {  n_ff, n_embd});
+@@ -8620,6 +8623,10 @@ struct llm_build_context {
+         // choose long/short freq factors based on the context size
+         const auto n_ctx_pre_seq = cparams.n_ctx / cparams.n_seq_max;
+ 
++        if (model.layers[il].rope_freqs != nullptr) {
++            return model.layers[il].rope_freqs;
++        }
++
+         if (n_ctx_pre_seq > hparams.n_ctx_orig_yarn) {
+             return model.layers[il].rope_long;
+         }
+@@ -8814,6 +8821,9 @@ struct llm_build_context {
+ 
+             // self-attention
+             {
++                // rope freq factors for llama3; may return nullptr for llama2 and other models
++                struct ggml_tensor * rope_factors = build_rope_factors(il);
++
+                 // compute Q and K and RoPE them
+                 struct ggml_tensor * Qcur = llm_build_lora_mm(lctx, ctx0, model.layers[il].wq, cur);
+                 cb(Qcur, "Qcur", il);
+@@ -8837,14 +8847,14 @@ struct llm_build_context {
+                 }
+ 
+                 Qcur = ggml_rope_ext(
+-                    ctx0, ggml_reshape_3d(ctx0, Qcur, n_embd_head, n_head, n_tokens), inp_pos, nullptr,
++                    ctx0, ggml_reshape_3d(ctx0, Qcur, n_embd_head, n_head, n_tokens), inp_pos, rope_factors,
+                     n_rot, rope_type, n_ctx_orig, freq_base, freq_scale,
+                     ext_factor, attn_factor, beta_fast, beta_slow
+                 );
+                 cb(Qcur, "Qcur", il);
+ 
+                 Kcur = ggml_rope_ext(
+-                    ctx0, ggml_reshape_3d(ctx0, Kcur, n_embd_head, n_head_kv, n_tokens), inp_pos, nullptr,
++                    ctx0, ggml_reshape_3d(ctx0, Kcur, n_embd_head, n_head_kv, n_tokens), inp_pos, rope_factors,
+                     n_rot, rope_type, n_ctx_orig, freq_base, freq_scale,
+                     ext_factor, attn_factor, beta_fast, beta_slow
+                 );
+-- 
+2.45.2
+

From 7c2a157ca4a9188c9d0e0c0a03a6bd9d163ba464 Mon Sep 17 00:00:00 2001
From: Daniel Hiltgen 
Date: Wed, 24 Jul 2024 13:43:26 -0700
Subject: [PATCH 146/384] Ensure amd gpu nodes are numerically sorted

For systems that enumerate over 10 CPUs the default lexicographical
sort order interleaves CPUs and GPUs.
---
 gpu/amd_linux.go | 15 +++++++++++++++
 1 file changed, 15 insertions(+)

diff --git a/gpu/amd_linux.go b/gpu/amd_linux.go
index 15b6fc61f..6493af9e1 100644
--- a/gpu/amd_linux.go
+++ b/gpu/amd_linux.go
@@ -10,6 +10,7 @@ import (
 	"path/filepath"
 	"regexp"
 	"slices"
+	"sort"
 	"strconv"
 	"strings"
 
@@ -82,6 +83,20 @@ func AMDGetGPUInfo() []RocmGPUInfo {
 	// The amdgpu driver always exposes the host CPU(s) first, but we have to skip them and subtract
 	// from the other IDs to get alignment with the HIP libraries expectations (zero is the first GPU, not the CPU)
 	matches, _ := filepath.Glob(GPUPropertiesFileGlob)
+	sort.Slice(matches, func(i, j int) bool {
+		// /sys/class/kfd/kfd/topology/nodes//properties
+		a, err := strconv.ParseInt(filepath.Base(filepath.Dir(matches[i])), 10, 64)
+		if err != nil {
+			slog.Debug("parse err", "error", err, "match", matches[i])
+			return false
+		}
+		b, err := strconv.ParseInt(filepath.Base(filepath.Dir(matches[j])), 10, 64)
+		if err != nil {
+			slog.Debug("parse err", "error", err, "match", matches[i])
+			return false
+		}
+		return a < b
+	})
 	cpuCount := 0
 	for _, match := range matches {
 		slog.Debug("evaluating amdgpu node " + match)

From 6c2129d5d0692f18e677c48d5ea7e015ecae5015 Mon Sep 17 00:00:00 2001
From: Daniel Hiltgen 
Date: Wed, 24 Jul 2024 15:22:00 -0700
Subject: [PATCH 147/384] Explain font problems on windows 10

---
 docs/windows.md | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/docs/windows.md b/docs/windows.md
index 69c2aa6d1..dbfc14407 100644
--- a/docs/windows.md
+++ b/docs/windows.md
@@ -23,6 +23,8 @@ Logs will often be helpful in diagnosing the problem (see
 * NVIDIA 452.39 or newer Drivers if you have an NVIDIA card
 * AMD Radeon Driver https://www.amd.com/en/support if you have a Radeon card
 
+Ollama uses unicode characters for progress indication, which may render as unknown squares in some older terminal fonts in Windows 10. If you see this, try changing your terminal font settings.
+
 ## API Access
 
 Here's a quick example showing API access from `powershell`

From ce3c93b08f0b90496e86b9e0a5753334c2d21419 Mon Sep 17 00:00:00 2001
From: Daniel Hiltgen 
Date: Wed, 24 Jul 2024 17:09:20 -0700
Subject: [PATCH 148/384] Report better error on cuda unsupported os/arch

If we detect an NVIDIA GPU, but nvidia doesn't support the os/arch,
this will report a better error for the user and point them to docs
to self-install the drivers if possible.
---
 scripts/install.sh | 20 +++++++++++++++++---
 1 file changed, 17 insertions(+), 3 deletions(-)

diff --git a/scripts/install.sh b/scripts/install.sh
index 2a06c350a..aa8b3e5e3 100644
--- a/scripts/install.sh
+++ b/scripts/install.sh
@@ -198,19 +198,29 @@ if check_gpu lspci amdgpu || check_gpu lshw amdgpu; then
     exit 0
 fi
 
+CUDA_REPO_ERR_MSG="NVIDIA GPU detected, but your OS and Architecture are not supported by NVIDIA.  Please install the CUDA driver manually https://docs.nvidia.com/cuda/cuda-installation-guide-linux/"
 # ref: https://docs.nvidia.com/cuda/cuda-installation-guide-linux/index.html#rhel-7-centos-7
 # ref: https://docs.nvidia.com/cuda/cuda-installation-guide-linux/index.html#rhel-8-rocky-8
 # ref: https://docs.nvidia.com/cuda/cuda-installation-guide-linux/index.html#rhel-9-rocky-9
 # ref: https://docs.nvidia.com/cuda/cuda-installation-guide-linux/index.html#fedora
 install_cuda_driver_yum() {
     status 'Installing NVIDIA repository...'
+    
     case $PACKAGE_MANAGER in
         yum)
             $SUDO $PACKAGE_MANAGER -y install yum-utils
-            $SUDO $PACKAGE_MANAGER-config-manager --add-repo https://developer.download.nvidia.com/compute/cuda/repos/$1$2/$(uname -m)/cuda-$1$2.repo
+            if curl -I --silent --fail --location "https://developer.download.nvidia.com/compute/cuda/repos/$1$2/$(uname -m)/cuda-$1$2.repo" >/dev/null ; then
+                $SUDO $PACKAGE_MANAGER-config-manager --add-repo https://developer.download.nvidia.com/compute/cuda/repos/$1$2/$(uname -m)/cuda-$1$2.repo
+            else
+                error $CUDA_REPO_ERR_MSG
+            fi
             ;;
         dnf)
-            $SUDO $PACKAGE_MANAGER config-manager --add-repo https://developer.download.nvidia.com/compute/cuda/repos/$1$2/$(uname -m)/cuda-$1$2.repo
+            if curl -I --silent --fail --location "https://developer.download.nvidia.com/compute/cuda/repos/$1$2/$(uname -m)/cuda-$1$2.repo" >/dev/null ; then
+                $SUDO $PACKAGE_MANAGER config-manager --add-repo https://developer.download.nvidia.com/compute/cuda/repos/$1$2/$(uname -m)/cuda-$1$2.repo
+            else
+                error $CUDA_REPO_ERR_MSG
+            fi
             ;;
     esac
 
@@ -235,7 +245,11 @@ install_cuda_driver_yum() {
 # ref: https://docs.nvidia.com/cuda/cuda-installation-guide-linux/index.html#debian
 install_cuda_driver_apt() {
     status 'Installing NVIDIA repository...'
-    curl -fsSL -o $TEMP_DIR/cuda-keyring.deb https://developer.download.nvidia.com/compute/cuda/repos/$1$2/$(uname -m)/cuda-keyring_1.1-1_all.deb
+    if curl -I --silent --fail --location "https://developer.download.nvidia.com/compute/cuda/repos/$1$2/$(uname -m)/cuda-keyring_1.1-1_all.deb" >/dev/null ; then
+        curl -fsSL -o $TEMP_DIR/cuda-keyring.deb https://developer.download.nvidia.com/compute/cuda/repos/$1$2/$(uname -m)/cuda-keyring_1.1-1_all.deb
+    else
+        error $CUDA_REPO_ERR_MSG
+    fi
 
     case $1 in
         debian)

From bbf8f102ee06bd6b149e4999571c0844aa47b12f Mon Sep 17 00:00:00 2001
From: Jeffrey Morgan 
Date: Thu, 25 Jul 2024 18:24:55 -0400
Subject: [PATCH 149/384] Revert "llm(llama): pass rope factors (#5924)"
 (#5963)

This reverts commit bb46bbcf5e90e5efab5ff946a6c798131907ba2d.
---
 llm/patches/0001-llama-3.1-rope-scaling.diff | 71 --------------------
 1 file changed, 71 deletions(-)
 delete mode 100644 llm/patches/0001-llama-3.1-rope-scaling.diff

diff --git a/llm/patches/0001-llama-3.1-rope-scaling.diff b/llm/patches/0001-llama-3.1-rope-scaling.diff
deleted file mode 100644
index 45dcb4f50..000000000
--- a/llm/patches/0001-llama-3.1-rope-scaling.diff
+++ /dev/null
@@ -1,71 +0,0 @@
-From 2f872f294fb6f5c6e8f983b68c40ea656053dd92 Mon Sep 17 00:00:00 2001
-From: Michael Yang 
-Date: Tue, 23 Jul 2024 14:33:29 -0700
-Subject: [PATCH] llama 3.1 rope scaling
-
----
- src/llama.cpp | 14 ++++++++++++--
- 1 file changed, 12 insertions(+), 2 deletions(-)
-
-diff --git a/src/llama.cpp b/src/llama.cpp
-index 8fe51971..a9969df8 100644
---- a/src/llama.cpp
-+++ b/src/llama.cpp
-@@ -2472,6 +2472,7 @@ struct llama_layer {
-     // long rope factors
-     struct ggml_tensor * rope_long  = nullptr;
-     struct ggml_tensor * rope_short = nullptr;
-+    struct ggml_tensor * rope_freqs = nullptr;
- 
-     // bitnet scale
-     struct ggml_tensor * wq_scale;
-@@ -6143,6 +6144,8 @@ static bool llm_load_tensors(
- 
-                         layer.ffn_norm = ml.create_tensor(ctx_layer, tn(LLM_TENSOR_FFN_NORM, "weight", i), {n_embd});
- 
-+                        layer.rope_freqs  = ml.create_tensor(ctx_layer, tn(LLM_TENSOR_ROPE_FREQS,  "weight"), { n_embd/n_head/2 }, llama_model_loader::TENSOR_NOT_REQUIRED | (i != 0 ? llama_model_loader::TENSOR_DUPLICATED : 0));
-+
-                         if (n_expert == 0) {
-                             layer.ffn_gate = ml.create_tensor(ctx_split, tn(LLM_TENSOR_FFN_GATE, "weight", i), {n_embd,   n_ff});
-                             layer.ffn_down = ml.create_tensor(ctx_split, tn(LLM_TENSOR_FFN_DOWN, "weight", i), {  n_ff, n_embd});
-@@ -8620,6 +8623,10 @@ struct llm_build_context {
-         // choose long/short freq factors based on the context size
-         const auto n_ctx_pre_seq = cparams.n_ctx / cparams.n_seq_max;
- 
-+        if (model.layers[il].rope_freqs != nullptr) {
-+            return model.layers[il].rope_freqs;
-+        }
-+
-         if (n_ctx_pre_seq > hparams.n_ctx_orig_yarn) {
-             return model.layers[il].rope_long;
-         }
-@@ -8814,6 +8821,9 @@ struct llm_build_context {
- 
-             // self-attention
-             {
-+                // rope freq factors for llama3; may return nullptr for llama2 and other models
-+                struct ggml_tensor * rope_factors = build_rope_factors(il);
-+
-                 // compute Q and K and RoPE them
-                 struct ggml_tensor * Qcur = llm_build_lora_mm(lctx, ctx0, model.layers[il].wq, cur);
-                 cb(Qcur, "Qcur", il);
-@@ -8837,14 +8847,14 @@ struct llm_build_context {
-                 }
- 
-                 Qcur = ggml_rope_ext(
--                    ctx0, ggml_reshape_3d(ctx0, Qcur, n_embd_head, n_head, n_tokens), inp_pos, nullptr,
-+                    ctx0, ggml_reshape_3d(ctx0, Qcur, n_embd_head, n_head, n_tokens), inp_pos, rope_factors,
-                     n_rot, rope_type, n_ctx_orig, freq_base, freq_scale,
-                     ext_factor, attn_factor, beta_fast, beta_slow
-                 );
-                 cb(Qcur, "Qcur", il);
- 
-                 Kcur = ggml_rope_ext(
--                    ctx0, ggml_reshape_3d(ctx0, Kcur, n_embd_head, n_head_kv, n_tokens), inp_pos, nullptr,
-+                    ctx0, ggml_reshape_3d(ctx0, Kcur, n_embd_head, n_head_kv, n_tokens), inp_pos, rope_factors,
-                     n_rot, rope_type, n_ctx_orig, freq_base, freq_scale,
-                     ext_factor, attn_factor, beta_fast, beta_slow
-                 );
--- 
-2.45.2
-

From 4de1370a9dcc88b79ddc2d4af2e8c954bdfa67a1 Mon Sep 17 00:00:00 2001
From: royjhan <65097070+royjhan@users.noreply.github.com>
Date: Thu, 25 Jul 2024 15:34:06 -0700
Subject: [PATCH 150/384] openai tools doc (#5617)

---
 docs/openai.md | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/docs/openai.md b/docs/openai.md
index 248ba74ae..e51d3194f 100644
--- a/docs/openai.md
+++ b/docs/openai.md
@@ -79,7 +79,7 @@ curl http://localhost:11434/v1/chat/completions \
 - [x] JSON mode
 - [x] Reproducible outputs
 - [ ] Vision
-- [ ] Function calling
+- [x] Tools
 - [ ] Logprobs
 
 #### Supported request fields
@@ -97,9 +97,9 @@ curl http://localhost:11434/v1/chat/completions \
 - [x] `temperature`
 - [x] `top_p`
 - [x] `max_tokens`
-- [ ] `logit_bias`
-- [ ] `tools`
+- [x] `tools`
 - [ ] `tool_choice`
+- [ ] `logit_bias`
 - [ ] `user`
 - [ ] `n`
 

From 455e61170d12d2b29ac2dfe5fa6444ae40a9ef7f Mon Sep 17 00:00:00 2001
From: Jeffrey Morgan 
Date: Thu, 25 Jul 2024 18:34:47 -0400
Subject: [PATCH 151/384] Update openai.md

---
 docs/openai.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/docs/openai.md b/docs/openai.md
index e51d3194f..04d56bd65 100644
--- a/docs/openai.md
+++ b/docs/openai.md
@@ -78,8 +78,8 @@ curl http://localhost:11434/v1/chat/completions \
 - [x] Streaming
 - [x] JSON mode
 - [x] Reproducible outputs
-- [ ] Vision
 - [x] Tools
+- [ ] Vision
 - [ ] Logprobs
 
 #### Supported request fields

From c8af3c2d969a99618eecf169bd75aa112573ac27 Mon Sep 17 00:00:00 2001
From: Blake Mizerany 
Date: Thu, 25 Jul 2024 15:58:30 -0700
Subject: [PATCH 152/384] server: reuse original download URL for images
 (#5962)

This changes the registry client to reuse the original download URL
it gets on the first redirect response for all subsequent requests,
preventing thundering herd issues when hot new LLMs are released.
---
 server/download.go | 75 +++++++++++++++++++++++++++++++++++++++++++++-
 server/images.go   |  6 +++-
 2 files changed, 79 insertions(+), 2 deletions(-)

diff --git a/server/download.go b/server/download.go
index d93cd3b45..8b5b577fd 100644
--- a/server/download.go
+++ b/server/download.go
@@ -8,6 +8,7 @@ import (
 	"io"
 	"log/slog"
 	"math"
+	"math/rand/v2"
 	"net/http"
 	"net/url"
 	"os"
@@ -141,6 +142,32 @@ func (b *blobDownload) Run(ctx context.Context, requestURL *url.URL, opts *regis
 	b.err = b.run(ctx, requestURL, opts)
 }
 
+func newBackoff(maxBackoff time.Duration) func(ctx context.Context) error {
+	var n int
+	return func(ctx context.Context) error {
+		if ctx.Err() != nil {
+			return ctx.Err()
+		}
+
+		n++
+
+		// n^2 backoff timer is a little smoother than the
+		// common choice of 2^n.
+		d := min(time.Duration(n*n)*10*time.Millisecond, maxBackoff)
+		// Randomize the delay between 0.5-1.5 x msec, in order
+		// to prevent accidental "thundering herd" problems.
+		d = time.Duration(float64(d) * (rand.Float64() + 0.5))
+		t := time.NewTimer(d)
+		defer t.Stop()
+		select {
+		case <-ctx.Done():
+			return ctx.Err()
+		case <-t.C:
+			return nil
+		}
+	}
+}
+
 func (b *blobDownload) run(ctx context.Context, requestURL *url.URL, opts *registryOptions) error {
 	defer blobDownloadManager.Delete(b.Digest)
 	ctx, b.CancelFunc = context.WithCancel(ctx)
@@ -153,6 +180,52 @@ func (b *blobDownload) run(ctx context.Context, requestURL *url.URL, opts *regis
 
 	_ = file.Truncate(b.Total)
 
+	directURL, err := func() (*url.URL, error) {
+		ctx, cancel := context.WithTimeout(ctx, 30*time.Second)
+		defer cancel()
+
+		backoff := newBackoff(10 * time.Second)
+		for {
+			// shallow clone opts to be used in the closure
+			// without affecting the outer opts.
+			newOpts := new(registryOptions)
+			*newOpts = *opts
+
+			newOpts.CheckRedirect = func(req *http.Request, via []*http.Request) error {
+				if len(via) > 10 {
+					return errors.New("maxium redirects exceeded (10) for directURL")
+				}
+
+				// if the hostname is the same, allow the redirect
+				if req.URL.Hostname() == requestURL.Hostname() {
+					return nil
+				}
+
+				// stop at the first redirect that is not
+				// the same hostname as the original
+				// request.
+				return http.ErrUseLastResponse
+			}
+
+			resp, err := makeRequestWithRetry(ctx, http.MethodGet, requestURL, nil, nil, newOpts)
+			if err != nil {
+				slog.Warn("failed to get direct URL; backing off and retrying", "err", err)
+				if err := backoff(ctx); err != nil {
+					return nil, err
+				}
+				continue
+			}
+			defer resp.Body.Close()
+			if resp.StatusCode != http.StatusTemporaryRedirect {
+				return nil, fmt.Errorf("unexpected status code %d", resp.StatusCode)
+			}
+			return resp.Location()
+		}
+	}()
+	if err != nil {
+		return err
+	}
+
 	g, inner := errgroup.WithContext(ctx)
 	g.SetLimit(numDownloadParts)
 	for i := range b.Parts {
@@ -165,7 +238,7 @@ func (b *blobDownload) run(ctx context.Context, requestURL *url.URL, opts *regis
 			var err error
 			for try := 0; try < maxRetries; try++ {
 				w := io.NewOffsetWriter(file, part.StartsAt())
-				err = b.downloadChunk(inner, requestURL, w, part, opts)
+				err = b.downloadChunk(inner, directURL, w, part, opts)
 				switch {
 				case errors.Is(err, context.Canceled), errors.Is(err, syscall.ENOSPC):
 					// return immediately if the context is canceled or the device is out of space
diff --git a/server/images.go b/server/images.go
index 574dec191..836dbcc2d 100644
--- a/server/images.go
+++ b/server/images.go
@@ -54,6 +54,8 @@ type registryOptions struct {
 	Username string
 	Password string
 	Token    string
+
+	CheckRedirect func(req *http.Request, via []*http.Request) error
 }
 
 type Model struct {
@@ -1131,7 +1133,9 @@ func makeRequest(ctx context.Context, method string, requestURL *url.URL, header
 		req.ContentLength = contentLength
 	}
 
-	resp, err := http.DefaultClient.Do(req)
+	resp, err := (&http.Client{
+		CheckRedirect: regOpts.CheckRedirect,
+	}).Do(req)
 	if err != nil {
 		return nil, err
 	}

From 997c903884b08aef53d0f92634f74bdb64f05c0a Mon Sep 17 00:00:00 2001
From: Michael Yang 
Date: Thu, 25 Jul 2024 16:23:40 -0700
Subject: [PATCH 153/384] Update docs/template.md

Co-authored-by: Jeffrey Morgan 
---
 docs/template.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/docs/template.md b/docs/template.md
index 8f41e8fb6..f6ce06ba4 100644
--- a/docs/template.md
+++ b/docs/template.md
@@ -24,7 +24,7 @@ In this example, we have:
 * Three variables: `Messages`, `Role`, and `Content` (variables)
 * A custom function (action) that iterates over an array of items (`range .Messages`) and displays each item
 
-## Adding Templates to Your Model
+## Adding templates to your model
 
 By default, models imported into Ollama have a default template of `{{ .Prompt }}`, i.e. user inputs are sent verbatim to the LLM. This is appropriate for text or code completion models but lacks essential markers for chat or instruction models.
 

From ae27d9dcfd32b7fbaa0d5a1fb0126106873332bf Mon Sep 17 00:00:00 2001
From: Jeffrey Morgan 
Date: Thu, 25 Jul 2024 20:27:33 -0400
Subject: [PATCH 154/384] Update openai.md

---
 docs/openai.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/docs/openai.md b/docs/openai.md
index 04d56bd65..fee30f71d 100644
--- a/docs/openai.md
+++ b/docs/openai.md
@@ -78,7 +78,7 @@ curl http://localhost:11434/v1/chat/completions \
 - [x] Streaming
 - [x] JSON mode
 - [x] Reproducible outputs
-- [x] Tools
+- [x] Tools (streaming support coming soon)
 - [ ] Vision
 - [ ] Logprobs
 

From f5e3939220e9cd3d7a636708bc9df031ebfd4854 Mon Sep 17 00:00:00 2001
From: Jeffrey Morgan 
Date: Thu, 25 Jul 2024 23:10:18 -0400
Subject: [PATCH 155/384] Update api.md (#5968)

---
 docs/api.md | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/docs/api.md b/docs/api.md
index 0ab70383a..2d4fe28f4 100644
--- a/docs/api.md
+++ b/docs/api.md
@@ -418,6 +418,7 @@ Generate the next message in a chat with a provided model. This is a streaming e
 
 - `model`: (required) the [model name](#model-names)
 - `messages`: the messages of the chat, this can be used to keep a chat memory
+- `tools`: tools for the model to use if supported. Requires `stream` to be set to `false`
 
 The `message` object has the following fields:
 
@@ -432,7 +433,6 @@ Advanced parameters (optional):
 - `options`: additional model parameters listed in the documentation for the [Modelfile](./modelfile.md#valid-parameters-and-values) such as `temperature`
 - `stream`: if `false` the response will be returned as a single response object, rather than a stream of objects
 - `keep_alive`: controls how long the model will stay loaded into memory following the request (default: `5m`)
-- `tools`: external tools the model can use. Not all models support this feature.
 
 ### Examples
 
@@ -1286,4 +1286,4 @@ curl http://localhost:11434/api/embeddings -d '{
     0.8785552978515625, -0.34576427936553955, 0.5742510557174683, -0.04222835972905159, -0.137906014919281
   ]
 }
-```
\ No newline at end of file
+```

From 15af5584238c17ae21853e7619e8008078e6e792 Mon Sep 17 00:00:00 2001
From: Michael Yang 
Date: Wed, 19 Jun 2024 14:14:28 -0700
Subject: [PATCH 156/384] include modelfile messages

---
 cmd/cmd.go       |  1 -
 server/images.go |  7 +------
 server/routes.go | 31 ++++++++++++++++++-------------
 3 files changed, 19 insertions(+), 20 deletions(-)

diff --git a/cmd/cmd.go b/cmd/cmd.go
index b761d018f..641afafbb 100644
--- a/cmd/cmd.go
+++ b/cmd/cmd.go
@@ -362,7 +362,6 @@ func RunHandler(cmd *cobra.Command, args []string) error {
 
 	opts.MultiModal = slices.Contains(info.Details.Families, "clip")
 	opts.ParentModel = info.Details.ParentModel
-	opts.Messages = append(opts.Messages, info.Messages...)
 
 	if interactive {
 		return generateInteractive(cmd, opts)
diff --git a/server/images.go b/server/images.go
index 836dbcc2d..0f6165516 100644
--- a/server/images.go
+++ b/server/images.go
@@ -70,7 +70,7 @@ type Model struct {
 	License        []string
 	Digest         string
 	Options        map[string]interface{}
-	Messages       []Message
+	Messages       []api.Message
 
 	Template *template.Template
 }
@@ -191,11 +191,6 @@ func (m *Model) String() string {
 	return modelfile.String()
 }
 
-type Message struct {
-	Role    string `json:"role"`
-	Content string `json:"content"`
-}
-
 type ConfigV2 struct {
 	ModelFormat   string   `json:"model_format"`
 	ModelFamily   string   `json:"model_family"`
diff --git a/server/routes.go b/server/routes.go
index e6ffe5268..2b4d5794f 100644
--- a/server/routes.go
+++ b/server/routes.go
@@ -164,17 +164,6 @@ func (s *Server) GenerateHandler(c *gin.Context) {
 			}
 		}
 
-		var b bytes.Buffer
-		if req.Context != nil {
-			s, err := r.Detokenize(c.Request.Context(), req.Context)
-			if err != nil {
-				c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
-				return
-			}
-
-			b.WriteString(s)
-		}
-
 		var values template.Values
 		if req.Suffix != "" {
 			values.Prompt = prompt
@@ -187,6 +176,10 @@ func (s *Server) GenerateHandler(c *gin.Context) {
 				msgs = append(msgs, api.Message{Role: "system", Content: m.System})
 			}
 
+			if req.Context == nil {
+				msgs = append(msgs, m.Messages...)
+			}
+
 			for _, i := range images {
 				msgs = append(msgs, api.Message{Role: "user", Content: fmt.Sprintf("[img-%d]", i.ID)})
 			}
@@ -194,11 +187,22 @@ func (s *Server) GenerateHandler(c *gin.Context) {
 			values.Messages = append(msgs, api.Message{Role: "user", Content: req.Prompt})
 		}
 
+		var b bytes.Buffer
 		if err := tmpl.Execute(&b, values); err != nil {
 			c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
 			return
 		}
 
+		if req.Context != nil {
+			s, err := r.Detokenize(c.Request.Context(), req.Context)
+			if err != nil {
+				c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
+				return
+			}
+
+			b.WriteString(s)
+		}
+
 		prompt = b.String()
 	}
 
@@ -1323,11 +1327,12 @@ func (s *Server) ChatHandler(c *gin.Context) {
 		return
 	}
 
+	msgs := append(m.Messages, req.Messages...)
 	if req.Messages[0].Role != "system" && m.System != "" {
-		req.Messages = append([]api.Message{{Role: "system", Content: m.System}}, req.Messages...)
+		msgs = append([]api.Message{{Role: "system", Content: m.System}}, msgs...)
 	}
 
-	prompt, images, err := chatPrompt(c.Request.Context(), m, r.Tokenize, opts, req.Messages, req.Tools)
+	prompt, images, err := chatPrompt(c.Request.Context(), m, r.Tokenize, opts, msgs, req.Tools)
 	if err != nil {
 		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
 		return

From 3d9de805b777ca43746a6ae951b34689aa16e8e9 Mon Sep 17 00:00:00 2001
From: Michael Yang 
Date: Fri, 26 Jul 2024 13:19:01 -0700
Subject: [PATCH 157/384] fix: model save

stop parameter is saved as a slice which is incompatible with modelfile
parsing
---
 cmd/interactive.go      | 46 ++++++++++++++-----------
 cmd/interactive_test.go | 75 +++++++++++++++++++----------------------
 2 files changed, 60 insertions(+), 61 deletions(-)

diff --git a/cmd/interactive.go b/cmd/interactive.go
index adbc3e9fb..2f83269e8 100644
--- a/cmd/interactive.go
+++ b/cmd/interactive.go
@@ -1,6 +1,7 @@
 package cmd
 
 import (
+	"cmp"
 	"errors"
 	"fmt"
 	"io"
@@ -9,13 +10,14 @@ import (
 	"path/filepath"
 	"regexp"
 	"slices"
-	"sort"
 	"strings"
 
 	"github.com/spf13/cobra"
+	"golang.org/x/exp/maps"
 
 	"github.com/ollama/ollama/api"
 	"github.com/ollama/ollama/envconfig"
+	"github.com/ollama/ollama/parser"
 	"github.com/ollama/ollama/progress"
 	"github.com/ollama/ollama/readline"
 	"github.com/ollama/ollama/types/errtypes"
@@ -375,9 +377,9 @@ func generateInteractive(cmd *cobra.Command, opts runOptions) error {
 					return err
 				}
 				req := &api.ShowRequest{
-					Name:     opts.Model,
-					System:   opts.System,
-					Options:  opts.Options,
+					Name:    opts.Model,
+					System:  opts.System,
+					Options: opts.Options,
 				}
 				resp, err := client.Show(cmd.Context(), req)
 				if err != nil {
@@ -506,31 +508,35 @@ func generateInteractive(cmd *cobra.Command, opts runOptions) error {
 }
 
 func buildModelfile(opts runOptions) string {
-	var mf strings.Builder
-	model := opts.ParentModel
-	if model == "" {
-		model = opts.Model
-	}
-	fmt.Fprintf(&mf, "FROM %s\n", model)
+	var f parser.File
+	f.Commands = append(f.Commands, parser.Command{Name: "model", Args: cmp.Or(opts.ParentModel, opts.Model)})
+
 	if opts.System != "" {
-		fmt.Fprintf(&mf, "SYSTEM \"\"\"%s\"\"\"\n", opts.System)
+		f.Commands = append(f.Commands, parser.Command{Name: "system", Args: opts.System})
 	}
 
-	keys := make([]string, 0)
-	for k := range opts.Options {
-		keys = append(keys, k)
-	}
-	sort.Strings(keys)
+	keys := maps.Keys(opts.Options)
+	slices.Sort(keys)
 	for _, k := range keys {
-		fmt.Fprintf(&mf, "PARAMETER %s %v\n", k, opts.Options[k])
+		v := opts.Options[k]
+		var cmds []parser.Command
+		switch t := v.(type) {
+		case []string:
+			for _, s := range t {
+				cmds = append(cmds, parser.Command{Name: k, Args: s})
+			}
+		default:
+			cmds = append(cmds, parser.Command{Name: k, Args: fmt.Sprintf("%v", t)})
+		}
+
+		f.Commands = append(f.Commands, cmds...)
 	}
-	fmt.Fprintln(&mf)
 
 	for _, msg := range opts.Messages {
-		fmt.Fprintf(&mf, "MESSAGE %s \"\"\"%s\"\"\"\n", msg.Role, msg.Content)
+		f.Commands = append(f.Commands, parser.Command{Name: "message", Args: fmt.Sprintf("%s: %s", msg.Role, msg.Content)})
 	}
 
-	return mf.String()
+	return f.String()
 }
 
 func normalizeFilePath(fp string) string {
diff --git a/cmd/interactive_test.go b/cmd/interactive_test.go
index 711f38604..bb7e0abaf 100644
--- a/cmd/interactive_test.go
+++ b/cmd/interactive_test.go
@@ -1,12 +1,10 @@
 package cmd
 
 import (
-	"bytes"
 	"testing"
-	"text/template"
 
+	"github.com/google/go-cmp/cmp"
 	"github.com/stretchr/testify/assert"
-	"github.com/stretchr/testify/require"
 
 	"github.com/ollama/ollama/api"
 )
@@ -57,58 +55,53 @@ d:\path with\spaces\seven.svg inbetween7 c:\users\jdoe\eight.png inbetween8
 
 func TestModelfileBuilder(t *testing.T) {
 	opts := runOptions{
-		Model:    "hork",
-		System:   "You are part horse and part shark, but all hork. Do horklike things",
+		Model:  "hork",
+		System: "You are part horse and part shark, but all hork. Do horklike things",
 		Messages: []api.Message{
 			{Role: "user", Content: "Hey there hork!"},
 			{Role: "assistant", Content: "Yes it is true, I am half horse, half shark."},
 		},
-		Options: map[string]interface{}{},
+		Options: map[string]any{
+			"temperature":      0.9,
+			"seed":             42,
+			"penalize_newline": false,
+			"stop":             []string{"hi", "there"},
+		},
 	}
 
-	opts.Options["temperature"] = 0.9
-	opts.Options["seed"] = 42
-	opts.Options["penalize_newline"] = false
-	opts.Options["stop"] = []string{"hi", "there"}
-
-	mf := buildModelfile(opts)
-	expectedModelfile := `FROM {{.Model}}
-SYSTEM """{{.System}}"""
+	t.Run("model", func(t *testing.T) {
+		expect := `FROM hork
+SYSTEM You are part horse and part shark, but all hork. Do horklike things
 PARAMETER penalize_newline false
 PARAMETER seed 42
-PARAMETER stop [hi there]
+PARAMETER stop hi
+PARAMETER stop there
 PARAMETER temperature 0.9
-
-MESSAGE user """Hey there hork!"""
-MESSAGE assistant """Yes it is true, I am half horse, half shark."""
+MESSAGE user Hey there hork!
+MESSAGE assistant Yes it is true, I am half horse, half shark.
 `
 
-	tmpl, err := template.New("").Parse(expectedModelfile)
-	require.NoError(t, err)
+		actual := buildModelfile(opts)
+		if diff := cmp.Diff(expect, actual); diff != "" {
+			t.Errorf("mismatch (-want +got):\n%s", diff)
+		}
+	})
 
-	var buf bytes.Buffer
-	err = tmpl.Execute(&buf, opts)
-	require.NoError(t, err)
-	assert.Equal(t, buf.String(), mf)
-
-	opts.ParentModel = "horseshark"
-	mf = buildModelfile(opts)
-	expectedModelfile = `FROM {{.ParentModel}}
-SYSTEM """{{.System}}"""
+	t.Run("parent model", func(t *testing.T) {
+		opts.ParentModel = "horseshark"
+		expect := `FROM horseshark
+SYSTEM You are part horse and part shark, but all hork. Do horklike things
 PARAMETER penalize_newline false
 PARAMETER seed 42
-PARAMETER stop [hi there]
+PARAMETER stop hi
+PARAMETER stop there
 PARAMETER temperature 0.9
-
-MESSAGE user """Hey there hork!"""
-MESSAGE assistant """Yes it is true, I am half horse, half shark."""
+MESSAGE user Hey there hork!
+MESSAGE assistant Yes it is true, I am half horse, half shark.
 `
-
-	tmpl, err = template.New("").Parse(expectedModelfile)
-	require.NoError(t, err)
-
-	var parentBuf bytes.Buffer
-	err = tmpl.Execute(&parentBuf, opts)
-	require.NoError(t, err)
-	assert.Equal(t, parentBuf.String(), mf)
+		actual := buildModelfile(opts)
+		if diff := cmp.Diff(expect, actual); diff != "" {
+			t.Errorf("mismatch (-want +got):\n%s", diff)
+		}
+	})
 }

From a250c2cb13fd74b516dd138daad9ca54e30a9fab Mon Sep 17 00:00:00 2001
From: Michael Yang 
Date: Fri, 26 Jul 2024 13:39:38 -0700
Subject: [PATCH 158/384] display messages

---
 cmd/cmd.go         | 16 ++++++++++++++++
 cmd/interactive.go | 27 ++++-----------------------
 2 files changed, 20 insertions(+), 23 deletions(-)

diff --git a/cmd/cmd.go b/cmd/cmd.go
index 641afafbb..22950885d 100644
--- a/cmd/cmd.go
+++ b/cmd/cmd.go
@@ -364,6 +364,22 @@ func RunHandler(cmd *cobra.Command, args []string) error {
 	opts.ParentModel = info.Details.ParentModel
 
 	if interactive {
+		if err := loadModel(cmd, &opts); err != nil {
+			return err
+		}
+
+		for _, msg := range info.Messages {
+			switch msg.Role {
+			case "user":
+				fmt.Printf(">>> %s\n", msg.Content)
+			case "assistant":
+				state := &displayResponseState{}
+				displayResponse(msg.Content, opts.WordWrap, state)
+				fmt.Println()
+				fmt.Println()
+			}
+		}
+
 		return generateInteractive(cmd, opts)
 	}
 	return generate(cmd, opts)
diff --git a/cmd/interactive.go b/cmd/interactive.go
index adbc3e9fb..41b19971f 100644
--- a/cmd/interactive.go
+++ b/cmd/interactive.go
@@ -46,29 +46,10 @@ func loadModel(cmd *cobra.Command, opts *runOptions) error {
 		KeepAlive: opts.KeepAlive,
 	}
 
-	return client.Chat(cmd.Context(), chatReq, func(resp api.ChatResponse) error {
-		p.StopAndClear()
-		for _, msg := range opts.Messages {
-			switch msg.Role {
-			case "user":
-				fmt.Printf(">>> %s\n", msg.Content)
-			case "assistant":
-				state := &displayResponseState{}
-				displayResponse(msg.Content, opts.WordWrap, state)
-				fmt.Println()
-				fmt.Println()
-			}
-		}
-		return nil
-	})
+	return client.Chat(cmd.Context(), chatReq, func(api.ChatResponse) error { return nil })
 }
 
 func generateInteractive(cmd *cobra.Command, opts runOptions) error {
-	err := loadModel(cmd, &opts)
-	if err != nil {
-		return err
-	}
-
 	usage := func() {
 		fmt.Fprintln(os.Stderr, "Available Commands:")
 		fmt.Fprintln(os.Stderr, "  /set            Set session variables")
@@ -375,9 +356,9 @@ func generateInteractive(cmd *cobra.Command, opts runOptions) error {
 					return err
 				}
 				req := &api.ShowRequest{
-					Name:     opts.Model,
-					System:   opts.System,
-					Options:  opts.Options,
+					Name:    opts.Model,
+					System:  opts.System,
+					Options: opts.Options,
 				}
 				resp, err := client.Show(cmd.Context(), req)
 				if err != nil {

From a622c47bd32e4c7d8d6cd12ba8c7556fcc492524 Mon Sep 17 00:00:00 2001
From: Michael Yang 
Date: Fri, 26 Jul 2024 14:10:18 -0700
Subject: [PATCH 159/384] fix nil deref in auth.go

---
 server/auth.go   | 2 +-
 server/upload.go | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/server/auth.go b/server/auth.go
index e92a5b657..dcef5bf9c 100644
--- a/server/auth.go
+++ b/server/auth.go
@@ -67,7 +67,7 @@ func getAuthorizationToken(ctx context.Context, challenge registryChallenge) (st
 
 	headers.Add("Authorization", signature)
 
-	response, err := makeRequest(ctx, http.MethodGet, redirectURL, headers, nil, nil)
+	response, err := makeRequest(ctx, http.MethodGet, redirectURL, headers, nil, ®istryOptions{})
 	if err != nil {
 		return "", err
 	}
diff --git a/server/upload.go b/server/upload.go
index 73ce78cee..c4078c22b 100644
--- a/server/upload.go
+++ b/server/upload.go
@@ -254,7 +254,7 @@ func (b *blobUpload) uploadPart(ctx context.Context, method string, requestURL *
 
 		// retry uploading to the redirect URL
 		for try := range maxRetries {
-			err = b.uploadPart(ctx, http.MethodPut, redirectURL, part, nil)
+			err = b.uploadPart(ctx, http.MethodPut, redirectURL, part, ®istryOptions{})
 			switch {
 			case errors.Is(err, context.Canceled):
 				return err

From 750c1c55f7ea65219e4e24d6107a4a3ad519b53f Mon Sep 17 00:00:00 2001
From: Blake Mizerany 
Date: Fri, 26 Jul 2024 14:24:24 -0700
Subject: [PATCH 160/384] server: fix race conditions during download (#5994)

This fixes various data races scattered throughout the download/pull
client where the client was accessing the download state concurrently.

This commit is mostly a hot-fix and will be replaced by a new client one
day soon.

Also, remove the unnecessary opts argument from downloadChunk.
---
 server/download.go | 59 ++++++++++++++++++++++++++++------------------
 1 file changed, 36 insertions(+), 23 deletions(-)

diff --git a/server/download.go b/server/download.go
index 8b5b577fd..45483ba68 100644
--- a/server/download.go
+++ b/server/download.go
@@ -44,17 +44,19 @@ type blobDownload struct {
 
 	context.CancelFunc
 
-	done       bool
+	done       chan struct{}
 	err        error
 	references atomic.Int32
 }
 
 type blobDownloadPart struct {
-	N           int
-	Offset      int64
-	Size        int64
-	Completed   int64
-	lastUpdated time.Time
+	N         int
+	Offset    int64
+	Size      int64
+	Completed atomic.Int64
+
+	lastUpdatedMu sync.Mutex
+	lastUpdated   time.Time
 
 	*blobDownload `json:"-"`
 }
@@ -72,7 +74,7 @@ func (p *blobDownloadPart) Name() string {
 }
 
 func (p *blobDownloadPart) StartsAt() int64 {
-	return p.Offset + p.Completed
+	return p.Offset + p.Completed.Load()
 }
 
 func (p *blobDownloadPart) StopsAt() int64 {
@@ -82,7 +84,9 @@ func (p *blobDownloadPart) StopsAt() int64 {
 func (p *blobDownloadPart) Write(b []byte) (n int, err error) {
 	n = len(b)
 	p.blobDownload.Completed.Add(int64(n))
+	p.lastUpdatedMu.Lock()
 	p.lastUpdated = time.Now()
+	p.lastUpdatedMu.Unlock()
 	return n, nil
 }
 
@@ -92,6 +96,8 @@ func (b *blobDownload) Prepare(ctx context.Context, requestURL *url.URL, opts *r
 		return err
 	}
 
+	b.done = make(chan struct{})
+
 	for _, partFilePath := range partFilePaths {
 		part, err := b.readPart(partFilePath)
 		if err != nil {
@@ -99,7 +105,7 @@ func (b *blobDownload) Prepare(ctx context.Context, requestURL *url.URL, opts *r
 		}
 
 		b.Total += part.Size
-		b.Completed.Add(part.Completed)
+		b.Completed.Add(part.Completed.Load())
 		b.Parts = append(b.Parts, part)
 	}
 
@@ -139,6 +145,7 @@ func (b *blobDownload) Prepare(ctx context.Context, requestURL *url.URL, opts *r
 }
 
 func (b *blobDownload) Run(ctx context.Context, requestURL *url.URL, opts *registryOptions) {
+	defer close(b.done)
 	b.err = b.run(ctx, requestURL, opts)
 }
 
@@ -230,7 +237,7 @@ func (b *blobDownload) run(ctx context.Context, requestURL *url.URL, opts *regis
 	g.SetLimit(numDownloadParts)
 	for i := range b.Parts {
 		part := b.Parts[i]
-		if part.Completed == part.Size {
+		if part.Completed.Load() == part.Size {
 			continue
 		}
 
@@ -238,7 +245,7 @@ func (b *blobDownload) run(ctx context.Context, requestURL *url.URL, opts *regis
 			var err error
 			for try := 0; try < maxRetries; try++ {
 				w := io.NewOffsetWriter(file, part.StartsAt())
-				err = b.downloadChunk(inner, directURL, w, part, opts)
+				err = b.downloadChunk(inner, directURL, w, part)
 				switch {
 				case errors.Is(err, context.Canceled), errors.Is(err, syscall.ENOSPC):
 					// return immediately if the context is canceled or the device is out of space
@@ -279,29 +286,31 @@ func (b *blobDownload) run(ctx context.Context, requestURL *url.URL, opts *regis
 		return err
 	}
 
-	b.done = true
 	return nil
 }
 
-func (b *blobDownload) downloadChunk(ctx context.Context, requestURL *url.URL, w io.Writer, part *blobDownloadPart, opts *registryOptions) error {
+func (b *blobDownload) downloadChunk(ctx context.Context, requestURL *url.URL, w io.Writer, part *blobDownloadPart) error {
 	g, ctx := errgroup.WithContext(ctx)
 	g.Go(func() error {
-		headers := make(http.Header)
-		headers.Set("Range", fmt.Sprintf("bytes=%d-%d", part.StartsAt(), part.StopsAt()-1))
-		resp, err := makeRequestWithRetry(ctx, http.MethodGet, requestURL, headers, nil, opts)
+		req, err := http.NewRequestWithContext(ctx, http.MethodGet, requestURL.String(), nil)
+		if err != nil {
+			return err
+		}
+		req.Header.Set("Range", fmt.Sprintf("bytes=%d-%d", part.StartsAt(), part.StopsAt()-1))
+		resp, err := http.DefaultClient.Do(req)
 		if err != nil {
 			return err
 		}
 		defer resp.Body.Close()
 
-		n, err := io.CopyN(w, io.TeeReader(resp.Body, part), part.Size-part.Completed)
+		n, err := io.CopyN(w, io.TeeReader(resp.Body, part), part.Size-part.Completed.Load())
 		if err != nil && !errors.Is(err, context.Canceled) && !errors.Is(err, io.ErrUnexpectedEOF) {
 			// rollback progress
 			b.Completed.Add(-n)
 			return err
 		}
 
-		part.Completed += n
+		part.Completed.Add(n)
 		if err := b.writePart(part.Name(), part); err != nil {
 			return err
 		}
@@ -315,15 +324,21 @@ func (b *blobDownload) downloadChunk(ctx context.Context, requestURL *url.URL, w
 		for {
 			select {
 			case <-ticker.C:
-				if part.Completed >= part.Size {
+				if part.Completed.Load() >= part.Size {
 					return nil
 				}
 
-				if !part.lastUpdated.IsZero() && time.Since(part.lastUpdated) > 5*time.Second {
+				part.lastUpdatedMu.Lock()
+				lastUpdated := part.lastUpdated
+				part.lastUpdatedMu.Unlock()
+
+				if !lastUpdated.IsZero() && time.Since(lastUpdated) > 5*time.Second {
 					const msg = "%s part %d stalled; retrying. If this persists, press ctrl-c to exit, then 'ollama pull' to find a faster connection."
 					slog.Info(fmt.Sprintf(msg, b.Digest[7:19], part.N))
 					// reset last updated
+					part.lastUpdatedMu.Lock()
 					part.lastUpdated = time.Time{}
+					part.lastUpdatedMu.Unlock()
 					return errPartStalled
 				}
 			case <-ctx.Done():
@@ -388,6 +403,8 @@ func (b *blobDownload) Wait(ctx context.Context, fn func(api.ProgressResponse))
 	ticker := time.NewTicker(60 * time.Millisecond)
 	for {
 		select {
+		case <-b.done:
+			return b.err
 		case <-ticker.C:
 			fn(api.ProgressResponse{
 				Status:    fmt.Sprintf("pulling %s", b.Digest[7:19]),
@@ -395,10 +412,6 @@ func (b *blobDownload) Wait(ctx context.Context, fn func(api.ProgressResponse))
 				Total:     b.Total,
 				Completed: b.Completed.Load(),
 			})
-
-			if b.done || b.err != nil {
-				return b.err
-			}
 		case <-ctx.Done():
 			return ctx.Err()
 		}

From f2a96c7d778249a7f911471b6a1532339e42fcf5 Mon Sep 17 00:00:00 2001
From: Jeffrey Morgan 
Date: Fri, 26 Jul 2024 18:20:52 -0400
Subject: [PATCH 161/384] llm: keep patch for llama 3 rope factors (#5987)

---
 llm/patches/10-llama3-rope.diff | 70 +++++++++++++++++++++++++++++++++
 1 file changed, 70 insertions(+)
 create mode 100644 llm/patches/10-llama3-rope.diff

diff --git a/llm/patches/10-llama3-rope.diff b/llm/patches/10-llama3-rope.diff
new file mode 100644
index 000000000..39f38fead
--- /dev/null
+++ b/llm/patches/10-llama3-rope.diff
@@ -0,0 +1,70 @@
+From 2f872f294fb6f5c6e8f983b68c40ea656053dd92 Mon Sep 17 00:00:00 2001
+From: Michael Yang 
+Date: Tue, 23 Jul 2024 14:33:29 -0700
+Subject: [PATCH] llama 3.1 rope scaling
+
+---
+ src/llama.cpp | 14 ++++++++++++--
+ 1 file changed, 12 insertions(+), 2 deletions(-)
+
+diff --git a/src/llama.cpp b/src/llama.cpp
+index 8fe51971..a9969df8 100644
+--- a/src/llama.cpp
++++ b/src/llama.cpp
+@@ -2472,6 +2472,7 @@ struct llama_layer {
+     // long rope factors
+     struct ggml_tensor * rope_long  = nullptr;
+     struct ggml_tensor * rope_short = nullptr;
++    struct ggml_tensor * rope_freqs = nullptr;
+ 
+     // bitnet scale
+     struct ggml_tensor * wq_scale;
+@@ -6143,6 +6144,8 @@ static bool llm_load_tensors(
+ 
+                         layer.ffn_norm = ml.create_tensor(ctx_layer, tn(LLM_TENSOR_FFN_NORM, "weight", i), {n_embd});
+ 
++                        layer.rope_freqs  = ml.create_tensor(ctx_layer, tn(LLM_TENSOR_ROPE_FREQS,  "weight"), { n_embd/n_head/2 }, llama_model_loader::TENSOR_NOT_REQUIRED | (i != 0 ? llama_model_loader::TENSOR_DUPLICATED : 0));
++
+                         if (n_expert == 0) {
+                             layer.ffn_gate = ml.create_tensor(ctx_split, tn(LLM_TENSOR_FFN_GATE, "weight", i), {n_embd,   n_ff});
+                             layer.ffn_down = ml.create_tensor(ctx_split, tn(LLM_TENSOR_FFN_DOWN, "weight", i), {  n_ff, n_embd});
+@@ -8620,6 +8623,10 @@ struct llm_build_context {
+         // choose long/short freq factors based on the context size
+         const auto n_ctx_pre_seq = cparams.n_ctx / cparams.n_seq_max;
+ 
++        if (model.layers[il].rope_freqs != nullptr) {
++            return model.layers[il].rope_freqs;
++        }
++
+         if (n_ctx_pre_seq > hparams.n_ctx_orig_yarn) {
+             return model.layers[il].rope_long;
+         }
+@@ -8814,6 +8821,9 @@ struct llm_build_context {
+ 
+             // self-attention
+             {
++                // rope freq factors for llama3; may return nullptr for llama2 and other models
++                struct ggml_tensor * rope_factors = build_rope_factors(il);
++
+                 // compute Q and K and RoPE them
+                 struct ggml_tensor * Qcur = llm_build_lora_mm(lctx, ctx0, model.layers[il].wq, cur);
+                 cb(Qcur, "Qcur", il);
+@@ -8837,14 +8847,14 @@ struct llm_build_context {
+                 }
+ 
+                 Qcur = ggml_rope_ext(
+-                    ctx0, ggml_reshape_3d(ctx0, Qcur, n_embd_head, n_head, n_tokens), inp_pos, nullptr,
++                    ctx0, ggml_reshape_3d(ctx0, Qcur, n_embd_head, n_head, n_tokens), inp_pos, rope_factors,
+                     n_rot, rope_type, n_ctx_orig, freq_base, freq_scale,
+                     ext_factor, attn_factor, beta_fast, beta_slow
+                 );
+                 cb(Qcur, "Qcur", il);
+ 
+                 Kcur = ggml_rope_ext(
+-                    ctx0, ggml_reshape_3d(ctx0, Kcur, n_embd_head, n_head_kv, n_tokens), inp_pos, nullptr,
++                    ctx0, ggml_reshape_3d(ctx0, Kcur, n_embd_head, n_head_kv, n_tokens), inp_pos, rope_factors,
+                     n_rot, rope_type, n_ctx_orig, freq_base, freq_scale,
+                     ext_factor, attn_factor, beta_fast, beta_slow
+                 );
+-- 
+2.45.2

From f3d7a481b75e0af89ae946d3923a239a3d835643 Mon Sep 17 00:00:00 2001
From: Tibor Schmidt 
Date: Sat, 27 Jul 2024 23:37:40 +0200
Subject: [PATCH 162/384] feat: add support for min_p (resolve #1142) (#1825)

---
 api/types.go          | 1 +
 cmd/interactive.go    | 1 +
 docs/api.md           | 1 +
 docs/modelfile.md     | 1 +
 llm/server.go         | 1 +
 parser/parser_test.go | 1 +
 6 files changed, 6 insertions(+)

diff --git a/api/types.go b/api/types.go
index 65a99c763..35121813f 100644
--- a/api/types.go
+++ b/api/types.go
@@ -209,6 +209,7 @@ type Options struct {
 	NumPredict       int      `json:"num_predict,omitempty"`
 	TopK             int      `json:"top_k,omitempty"`
 	TopP             float32  `json:"top_p,omitempty"`
+	MinP             float32  `json:"min_p,omitempty"`
 	TFSZ             float32  `json:"tfs_z,omitempty"`
 	TypicalP         float32  `json:"typical_p,omitempty"`
 	RepeatLastN      int      `json:"repeat_last_n,omitempty"`
diff --git a/cmd/interactive.go b/cmd/interactive.go
index adbc3e9fb..c3cdf6295 100644
--- a/cmd/interactive.go
+++ b/cmd/interactive.go
@@ -138,6 +138,7 @@ func generateInteractive(cmd *cobra.Command, opts runOptions) error {
 		fmt.Fprintln(os.Stderr, "  /set parameter num_predict       Max number of tokens to predict")
 		fmt.Fprintln(os.Stderr, "  /set parameter top_k             Pick from top k num of tokens")
 		fmt.Fprintln(os.Stderr, "  /set parameter top_p           Pick token based on sum of probabilities")
+		fmt.Fprintln(os.Stderr, "  /set parameter min_p           Pick token based on top token probability * min_p")
 		fmt.Fprintln(os.Stderr, "  /set parameter num_ctx           Set the context size")
 		fmt.Fprintln(os.Stderr, "  /set parameter temperature     Set creativity level")
 		fmt.Fprintln(os.Stderr, "  /set parameter repeat_penalty  How strongly to penalize repetitions")
diff --git a/docs/api.md b/docs/api.md
index 2d4fe28f4..90b41f3ea 100644
--- a/docs/api.md
+++ b/docs/api.md
@@ -336,6 +336,7 @@ curl http://localhost:11434/api/generate -d '{
     "num_predict": 100,
     "top_k": 20,
     "top_p": 0.9,
+    "min_p": 0.0,
     "tfs_z": 0.5,
     "typical_p": 0.7,
     "repeat_last_n": 33,
diff --git a/docs/modelfile.md b/docs/modelfile.md
index c3645b062..852bf96c1 100644
--- a/docs/modelfile.md
+++ b/docs/modelfile.md
@@ -141,6 +141,7 @@ PARAMETER  
 | num_predict    | Maximum number of tokens to predict when generating text. (Default: 128, -1 = infinite generation, -2 = fill context)                                                                                                                                   | int        | num_predict 42       |
 | top_k          | Reduces the probability of generating nonsense. A higher value (e.g. 100) will give more diverse answers, while a lower value (e.g. 10) will be more conservative. (Default: 40)                                                                        | int        | top_k 40             |
 | top_p          | Works together with top-k. A higher value (e.g., 0.95) will lead to more diverse text, while a lower value (e.g., 0.5) will generate more focused and conservative text. (Default: 0.9)                                                                 | float      | top_p 0.9            |
+| min_p          | Alternative to the top_p, and aims to ensure a balance of quality and variety. The parameter *p* represents the minimum probability for a token to be considered, relative to the probability of the most likely token. For example, with *p*=0.05 and the most likely token having a probability of 0.9, logits with a value less than 0.045 are filtered out. (Default: 0.0) | float      | min_p 0.05            |
 
 ### TEMPLATE
 
diff --git a/llm/server.go b/llm/server.go
index 557327732..8127960f0 100644
--- a/llm/server.go
+++ b/llm/server.go
@@ -727,6 +727,7 @@ func (s *llmServer) Completion(ctx context.Context, req CompletionRequest, fn fu
 		"temperature":       req.Options.Temperature,
 		"top_k":             req.Options.TopK,
 		"top_p":             req.Options.TopP,
+		"min_p":             req.Options.MinP,
 		"tfs_z":             req.Options.TFSZ,
 		"typical_p":         req.Options.TypicalP,
 		"repeat_last_n":     req.Options.RepeatLastN,
diff --git a/parser/parser_test.go b/parser/parser_test.go
index 2b5c4c888..48044bc0e 100644
--- a/parser/parser_test.go
+++ b/parser/parser_test.go
@@ -451,6 +451,7 @@ func TestParseFileParameters(t *testing.T) {
 		"num_predict 1":                {"num_predict", "1"},
 		"top_k 1":                      {"top_k", "1"},
 		"top_p 1.0":                    {"top_p", "1.0"},
+		"min_p 0.05":                   {"min_p", "0.05"},
 		"tfs_z 1.0":                    {"tfs_z", "1.0"},
 		"typical_p 1.0":                {"typical_p", "1.0"},
 		"repeat_last_n 1":              {"repeat_last_n", "1"},

From 2c01610616074ef631ba5248f226099547ee7f57 Mon Sep 17 00:00:00 2001
From: Michael 
Date: Sun, 28 Jul 2024 17:21:38 -0400
Subject: [PATCH 163/384] update readme to llama3.1 (#5933)

---
 README.md | 31 ++++++++++++++++---------------
 1 file changed, 16 insertions(+), 15 deletions(-)

diff --git a/README.md b/README.md
index e7b129431..65c3a0133 100644
--- a/README.md
+++ b/README.md
@@ -35,10 +35,10 @@ The official [Ollama Docker image](https://hub.docker.com/r/ollama/ollama) `olla
 
 ## Quickstart
 
-To run and chat with [Llama 3](https://ollama.com/library/llama3):
+To run and chat with [Llama 3.1](https://ollama.com/library/llama3.1):
 
 ```
-ollama run llama3
+ollama run llama3.1
 ```
 
 ## Model library
@@ -49,8 +49,9 @@ Here are some example models that can be downloaded:
 
 | Model              | Parameters | Size  | Download                       |
 | ------------------ | ---------- | ----- | ------------------------------ |
-| Llama 3            | 8B         | 4.7GB | `ollama run llama3`            |
-| Llama 3            | 70B        | 40GB  | `ollama run llama3:70b`        |
+| Llama 3.1          | 8B         | 4.7GB | `ollama run llama3.1`          |
+| Llama 3.1          | 70B        | 40GB  | `ollama run llama3.1:70b`      |
+| Llama 3.1          | 405B       | 231GB | `ollama run llama3.1:405b`     |
 | Phi 3 Mini         | 3.8B       | 2.3GB | `ollama run phi3`              |
 | Phi 3 Medium       | 14B        | 7.9GB | `ollama run phi3:medium`       |
 | Gemma 2            | 9B         | 5.5GB | `ollama run gemma2`            |
@@ -97,16 +98,16 @@ See the [guide](docs/import.md) on importing models for more information.
 
 ### Customize a prompt
 
-Models from the Ollama library can be customized with a prompt. For example, to customize the `llama3` model:
+Models from the Ollama library can be customized with a prompt. For example, to customize the `llama3.1` model:
 
 ```
-ollama pull llama3
+ollama pull llama3.1
 ```
 
 Create a `Modelfile`:
 
 ```
-FROM llama3
+FROM llama3.1
 
 # set the temperature to 1 [higher is more creative, lower is more coherent]
 PARAMETER temperature 1
@@ -141,7 +142,7 @@ ollama create mymodel -f ./Modelfile
 ### Pull a model
 
 ```
-ollama pull llama3
+ollama pull llama3.1
 ```
 
 > This command can also be used to update a local model. Only the diff will be pulled.
@@ -149,13 +150,13 @@ ollama pull llama3
 ### Remove a model
 
 ```
-ollama rm llama3
+ollama rm llama3.1
 ```
 
 ### Copy a model
 
 ```
-ollama cp llama3 my-model
+ollama cp llama3.1 my-model
 ```
 
 ### Multiline input
@@ -179,14 +180,14 @@ The image features a yellow smiley face, which is likely the central focus of th
 ### Pass the prompt as an argument
 
 ```
-$ ollama run llama3 "Summarize this file: $(cat README.md)"
+$ ollama run llama3.1 "Summarize this file: $(cat README.md)"
  Ollama is a lightweight, extensible framework for building and running language models on the local machine. It provides a simple API for creating, running, and managing models, as well as a library of pre-built models that can be easily used in a variety of applications.
 ```
 
 ### Show model information
 
 ```
-ollama show llama3
+ollama show llama3.1
 ```
 
 ### List models on your computer
@@ -214,7 +215,7 @@ Next, start the server:
 Finally, in a separate shell, run a model:
 
 ```
-./ollama run llama3
+./ollama run llama3.1
 ```
 
 ## REST API
@@ -225,7 +226,7 @@ Ollama has a REST API for running and managing models.
 
 ```
 curl http://localhost:11434/api/generate -d '{
-  "model": "llama3",
+  "model": "llama3.1",
   "prompt":"Why is the sky blue?"
 }'
 ```
@@ -234,7 +235,7 @@ curl http://localhost:11434/api/generate -d '{
 
 ```
 curl http://localhost:11434/api/chat -d '{
-  "model": "llama3",
+  "model": "llama3.1",
   "messages": [
     { "role": "user", "content": "why is the sky blue?" }
   ]

From 0e4d653687f81db40622e287a846245b319f1fbe Mon Sep 17 00:00:00 2001
From: Jeffrey Morgan 
Date: Sun, 28 Jul 2024 19:56:02 -0700
Subject: [PATCH 164/384] upate to `llama3.1` elsewhere in repo (#6032)

---
 app/ollama.iss                | 2 +-
 app/ollama_welcome.ps1        | 2 +-
 docs/docker.md                | 2 +-
 docs/faq.md                   | 2 +-
 docs/tutorials/langchainjs.md | 4 ++--
 macapp/src/app.tsx            | 2 +-
 6 files changed, 7 insertions(+), 7 deletions(-)

diff --git a/app/ollama.iss b/app/ollama.iss
index 6bedb9ff7..dc6178f7b 100644
--- a/app/ollama.iss
+++ b/app/ollama.iss
@@ -138,7 +138,7 @@ SetupAppRunningError=Another Ollama installer is running.%n%nPlease cancel or fi
 
 
 ;FinishedHeadingLabel=Run your first model
-;FinishedLabel=%nRun this command in a PowerShell or cmd terminal.%n%n%n    ollama run llama3
+;FinishedLabel=%nRun this command in a PowerShell or cmd terminal.%n%n%n    ollama run llama3.1
 ;ClickFinish=%n
 
 [Registry]
diff --git a/app/ollama_welcome.ps1 b/app/ollama_welcome.ps1
index 9af37a461..46777a3a6 100644
--- a/app/ollama_welcome.ps1
+++ b/app/ollama_welcome.ps1
@@ -4,5 +4,5 @@ write-host "Welcome to Ollama!"
 write-host ""
 write-host "Run your first model:"
 write-host ""
-write-host "`tollama run llama3"
+write-host "`tollama run llama3.1"
 write-host ""
\ No newline at end of file
diff --git a/docs/docker.md b/docs/docker.md
index 0b58562b7..a34c3291a 100644
--- a/docs/docker.md
+++ b/docs/docker.md
@@ -63,7 +63,7 @@ docker run -d --device /dev/kfd --device /dev/dri -v ollama:/root/.ollama -p 114
 Now you can run a model:
 
 ```
-docker exec -it ollama ollama run llama3
+docker exec -it ollama ollama run llama3.1
 ```
 
 ### Try different models
diff --git a/docs/faq.md b/docs/faq.md
index da1848f7f..f2f32af4e 100644
--- a/docs/faq.md
+++ b/docs/faq.md
@@ -227,7 +227,7 @@ curl http://localhost:11434/api/chat -d '{"model": "mistral"}'
 
 To preload a model using the CLI, use the command:
 ```shell
-ollama run llama3 ""
+ollama run llama3.1 ""
 ```
 
 ## How do I keep a model loaded in memory or make it unload immediately?
diff --git a/docs/tutorials/langchainjs.md b/docs/tutorials/langchainjs.md
index 4d60afb64..f925869b5 100644
--- a/docs/tutorials/langchainjs.md
+++ b/docs/tutorials/langchainjs.md
@@ -15,7 +15,7 @@ import { Ollama } from "@langchain/community/llms/ollama";
 
 const ollama = new Ollama({
   baseUrl: "http://localhost:11434",
-  model: "llama3",
+  model: "llama3.1",
 });
 
 const answer = await ollama.invoke(`why is the sky blue?`);
@@ -23,7 +23,7 @@ const answer = await ollama.invoke(`why is the sky blue?`);
 console.log(answer);
 ```
 
-That will get us the same thing as if we ran `ollama run llama3 "why is the sky blue"` in the terminal. But we want to load a document from the web to ask a question against. **Cheerio** is a great library for ingesting a webpage, and **LangChain** uses it in their **CheerioWebBaseLoader**. So let's install **Cheerio** and build that part of the app.
+That will get us the same thing as if we ran `ollama run llama3.1 "why is the sky blue"` in the terminal. But we want to load a document from the web to ask a question against. **Cheerio** is a great library for ingesting a webpage, and **LangChain** uses it in their **CheerioWebBaseLoader**. So let's install **Cheerio** and build that part of the app.
 
 ```bash
 npm install cheerio
diff --git a/macapp/src/app.tsx b/macapp/src/app.tsx
index ab17df603..a627e63de 100644
--- a/macapp/src/app.tsx
+++ b/macapp/src/app.tsx
@@ -19,7 +19,7 @@ export default function () {
   const [step, setStep] = useState(Step.WELCOME)
   const [commandCopied, setCommandCopied] = useState(false)
 
-  const command = 'ollama run llama3'
+  const command = 'ollama run llama3.1'
 
   return (
     
From 6f26e9322fd4639b4e414f8890b0213783e74d7c Mon Sep 17 00:00:00 2001 From: Veit Heller Date: Mon, 29 Jul 2024 17:50:53 +0200 Subject: [PATCH 165/384] Fix typo in image docs (#6041) --- docs/api.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api.md b/docs/api.md index 90b41f3ea..c0202ef3e 100644 --- a/docs/api.md +++ b/docs/api.md @@ -587,7 +587,7 @@ Final response: ##### Request -Send a chat message with a conversation history. +Send a chat message with images. The images should be provided as an array, with the individual images encoded in Base64. ```shell curl http://localhost:11434/api/chat -d '{ From f26aef9a8bfdd3e0f0d13cafe8bd371f29d9d877 Mon Sep 17 00:00:00 2001 From: Ikko Eltociear Ashimine Date: Tue, 30 Jul 2024 02:53:30 +0900 Subject: [PATCH 166/384] docs: update README.md (#6059) HuggingFace -> Hugging Face --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 65c3a0133..824b3761a 100644 --- a/README.md +++ b/README.md @@ -390,7 +390,7 @@ See the [API documentation](./docs/api.md) for all endpoints. - [Llama Coder](https://github.com/ex3ndr/llama-coder) (Copilot alternative using Ollama) - [Ollama Copilot](https://github.com/bernardo-bruning/ollama-copilot) (Proxy that allows you to use ollama as a copilot like Github copilot) - [twinny](https://github.com/rjmacarthy/twinny) (Copilot and Copilot chat alternative using Ollama) -- [Wingman-AI](https://github.com/RussellCanfield/wingman-ai) (Copilot code and chat alternative using Ollama and HuggingFace) +- [Wingman-AI](https://github.com/RussellCanfield/wingman-ai) (Copilot code and chat alternative using Ollama and Hugging Face) - [Page Assist](https://github.com/n4ze3m/page-assist) (Chrome Extension) - [AI Telegram Bot](https://github.com/tusharhero/aitelegrambot) (Telegram bot using Ollama in backend) - [AI ST Completion](https://github.com/yaroslavyaroslav/OpenAI-sublime-text) (Sublime Text 4 AI assistant plugin with Ollama support) From 68ee42f995a04bd163eb1c714f53d4c25ab25474 Mon Sep 17 00:00:00 2001 From: Jeffrey Morgan Date: Mon, 29 Jul 2024 13:20:26 -0700 Subject: [PATCH 167/384] update llama.cpp submodule to `6eeaeba1` (#6039) --- llm/ext_server/server.cpp | 9 --- llm/llama.cpp | 2 +- llm/patches/05-default-pretokenizer.diff | 10 ++-- llm/patches/09-lora.diff | 6 +- llm/patches/10-llama3-rope.diff | 70 ------------------------ 5 files changed, 8 insertions(+), 89 deletions(-) delete mode 100644 llm/patches/10-llama3-rope.diff diff --git a/llm/ext_server/server.cpp b/llm/ext_server/server.cpp index 14d921c04..0d51460c7 100644 --- a/llm/ext_server/server.cpp +++ b/llm/ext_server/server.cpp @@ -2438,15 +2438,6 @@ static void server_params_parse(int argc, char **argv, server_params &sparams, g params.lora_adapter.emplace_back(lora_adapter, std::stof(argv[i])); params.use_mmap = false; } - else if (arg == "--lora-base") - { - if (++i >= argc) - { - invalid_param = true; - break; - } - params.lora_base = argv[i]; - } else if (arg == "-v" || arg == "--verbose") { server_verbose = true; diff --git a/llm/llama.cpp b/llm/llama.cpp index d94c6e0cc..6eeaeba12 160000 --- a/llm/llama.cpp +++ b/llm/llama.cpp @@ -1 +1 @@ -Subproject commit d94c6e0ccbd29ee1ba4f44e9caa8682ad94df9fa +Subproject commit 6eeaeba126ff701f3e8f79f246805b7023709972 diff --git a/llm/patches/05-default-pretokenizer.diff b/llm/patches/05-default-pretokenizer.diff index 646bc49c3..0d40fc3ca 100644 --- a/llm/patches/05-default-pretokenizer.diff +++ b/llm/patches/05-default-pretokenizer.diff @@ -1,8 +1,8 @@ diff --git a/src/llama.cpp b/src/llama.cpp -index 8fe51971..7113ba64 100644 +index a207451f..2ddf431d 100644 --- a/src/llama.cpp +++ b/src/llama.cpp -@@ -5433,16 +5433,7 @@ static void llm_load_vocab( +@@ -5347,16 +5347,7 @@ static void llm_load_vocab( if (vocab.type == LLAMA_VOCAB_TYPE_BPE) { vocab.tokenizer_add_space_prefix = false; vocab.tokenizer_clean_spaces = true; @@ -20,9 +20,9 @@ index 8fe51971..7113ba64 100644 vocab.type_pre = LLAMA_VOCAB_PRE_TYPE_DEFAULT; } else if ( tokenizer_pre == "llama3" || -@@ -5526,7 +5517,8 @@ static void llm_load_vocab( - vocab.type_pre = LLAMA_VOCAB_PRE_TYPE_SMOLLM; - vocab.tokenizer_clean_spaces = false; +@@ -5443,7 +5434,8 @@ static void llm_load_vocab( + tokenizer_pre == "codeshell") { + vocab.type_pre = LLAMA_VOCAB_PRE_TYPE_CODESHELL; } else { - throw std::runtime_error(format("unknown pre-tokenizer type: '%s'", tokenizer_pre.c_str())); + LLAMA_LOG_WARN("%s: missing or unrecognized pre-tokenizer type, using: 'default'\n", __func__); diff --git a/llm/patches/09-lora.diff b/llm/patches/09-lora.diff index fc1017a65..10c66d1d5 100644 --- a/llm/patches/09-lora.diff +++ b/llm/patches/09-lora.diff @@ -2,7 +2,7 @@ diff --git a/common/common.cpp b/common/common.cpp index dbb724fb..c26fe6ee 100644 --- a/common/common.cpp +++ b/common/common.cpp -@@ -2087,14 +2087,29 @@ std::tuple llama_init_from_gpt_par +@@ -2087,14 +2087,27 @@ std::tuple llama_init_from_gpt_par for (unsigned int i = 0; i < params.lora_adapter.size(); ++i) { const std::string & lora_adapter = std::get<0>(params.lora_adapter[i]); float lora_scale = std::get<1>(params.lora_adapter[i]); @@ -20,9 +20,7 @@ index dbb724fb..c26fe6ee 100644 + int err = llama_model_apply_lora_from_file(model, + lora_adapter.c_str(), + lora_scale, -+ ((i > 0) || params.lora_base.empty()) -+ ? NULL -+ : params.lora_base.c_str(), ++ nullptr, + params.n_threads); + if (err != 0) { + fprintf(stderr, "%s: error: failed to apply lora adapter\n", __func__); diff --git a/llm/patches/10-llama3-rope.diff b/llm/patches/10-llama3-rope.diff deleted file mode 100644 index 39f38fead..000000000 --- a/llm/patches/10-llama3-rope.diff +++ /dev/null @@ -1,70 +0,0 @@ -From 2f872f294fb6f5c6e8f983b68c40ea656053dd92 Mon Sep 17 00:00:00 2001 -From: Michael Yang -Date: Tue, 23 Jul 2024 14:33:29 -0700 -Subject: [PATCH] llama 3.1 rope scaling - ---- - src/llama.cpp | 14 ++++++++++++-- - 1 file changed, 12 insertions(+), 2 deletions(-) - -diff --git a/src/llama.cpp b/src/llama.cpp -index 8fe51971..a9969df8 100644 ---- a/src/llama.cpp -+++ b/src/llama.cpp -@@ -2472,6 +2472,7 @@ struct llama_layer { - // long rope factors - struct ggml_tensor * rope_long = nullptr; - struct ggml_tensor * rope_short = nullptr; -+ struct ggml_tensor * rope_freqs = nullptr; - - // bitnet scale - struct ggml_tensor * wq_scale; -@@ -6143,6 +6144,8 @@ static bool llm_load_tensors( - - layer.ffn_norm = ml.create_tensor(ctx_layer, tn(LLM_TENSOR_FFN_NORM, "weight", i), {n_embd}); - -+ layer.rope_freqs = ml.create_tensor(ctx_layer, tn(LLM_TENSOR_ROPE_FREQS, "weight"), { n_embd/n_head/2 }, llama_model_loader::TENSOR_NOT_REQUIRED | (i != 0 ? llama_model_loader::TENSOR_DUPLICATED : 0)); -+ - if (n_expert == 0) { - layer.ffn_gate = ml.create_tensor(ctx_split, tn(LLM_TENSOR_FFN_GATE, "weight", i), {n_embd, n_ff}); - layer.ffn_down = ml.create_tensor(ctx_split, tn(LLM_TENSOR_FFN_DOWN, "weight", i), { n_ff, n_embd}); -@@ -8620,6 +8623,10 @@ struct llm_build_context { - // choose long/short freq factors based on the context size - const auto n_ctx_pre_seq = cparams.n_ctx / cparams.n_seq_max; - -+ if (model.layers[il].rope_freqs != nullptr) { -+ return model.layers[il].rope_freqs; -+ } -+ - if (n_ctx_pre_seq > hparams.n_ctx_orig_yarn) { - return model.layers[il].rope_long; - } -@@ -8814,6 +8821,9 @@ struct llm_build_context { - - // self-attention - { -+ // rope freq factors for llama3; may return nullptr for llama2 and other models -+ struct ggml_tensor * rope_factors = build_rope_factors(il); -+ - // compute Q and K and RoPE them - struct ggml_tensor * Qcur = llm_build_lora_mm(lctx, ctx0, model.layers[il].wq, cur); - cb(Qcur, "Qcur", il); -@@ -8837,14 +8847,14 @@ struct llm_build_context { - } - - Qcur = ggml_rope_ext( -- ctx0, ggml_reshape_3d(ctx0, Qcur, n_embd_head, n_head, n_tokens), inp_pos, nullptr, -+ ctx0, ggml_reshape_3d(ctx0, Qcur, n_embd_head, n_head, n_tokens), inp_pos, rope_factors, - n_rot, rope_type, n_ctx_orig, freq_base, freq_scale, - ext_factor, attn_factor, beta_fast, beta_slow - ); - cb(Qcur, "Qcur", il); - - Kcur = ggml_rope_ext( -- ctx0, ggml_reshape_3d(ctx0, Kcur, n_embd_head, n_head_kv, n_tokens), inp_pos, nullptr, -+ ctx0, ggml_reshape_3d(ctx0, Kcur, n_embd_head, n_head_kv, n_tokens), inp_pos, rope_factors, - n_rot, rope_type, n_ctx_orig, freq_base, freq_scale, - ext_factor, attn_factor, beta_fast, beta_slow - ); --- -2.45.2 From 46e6327e0f85b046f5f92995d7f59146d347cd70 Mon Sep 17 00:00:00 2001 From: Jeffrey Morgan Date: Mon, 29 Jul 2024 13:35:16 -0700 Subject: [PATCH 168/384] api: add stringifier for `Tool` (#5891) --- api/types.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/api/types.go b/api/types.go index 35121813f..ea5161ff6 100644 --- a/api/types.go +++ b/api/types.go @@ -114,6 +114,11 @@ func (t Tools) String() string { return string(bts) } +func (t Tool) String() string { + bts, _ := json.Marshal(t) + return string(bts) +} + // Message is a single message in a chat sequence. The message contains the // role ("system", "user", or "assistant"), the content and an optional list // of images. From 365431d40617b85d0308fec8d0bd9c0cdb1ab3a4 Mon Sep 17 00:00:00 2001 From: royjhan <65097070+royjhan@users.noreply.github.com> Date: Mon, 29 Jul 2024 13:56:57 -0700 Subject: [PATCH 169/384] return tool calls finish reason for openai (#5995) * hot fix * backend stream support * clean up * finish reason * move to openai --- openai/openai.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/openai/openai.go b/openai/openai.go index de6f4bd59..5bd806604 100644 --- a/openai/openai.go +++ b/openai/openai.go @@ -218,6 +218,9 @@ func toChatCompletion(id string, r api.ChatResponse) ChatCompletion { Index: 0, Message: Message{Role: r.Message.Role, Content: r.Message.Content, ToolCalls: toolCalls}, FinishReason: func(reason string) *string { + if len(toolCalls) > 0 { + reason = "tool_calls" + } if len(reason) > 0 { return &reason } From 0be8baad2b684cda667fa5d48bf334382913a09c Mon Sep 17 00:00:00 2001 From: Kim Hallberg Date: Tue, 30 Jul 2024 08:56:37 +0200 Subject: [PATCH 170/384] Update and Fix example models (#6065) * Update example models * Remove unused README.md --- examples/go-chat/main.go | 2 +- examples/go-generate-streaming/main.go | 2 +- examples/go-generate/main.go | 2 +- examples/go-http-generate/README.md | 0 examples/langchain-python-rag-document/README.md | 8 ++++++++ examples/langchain-python-rag-document/main.py | 2 +- examples/langchain-python-rag-websummary/README.md | 4 ++-- examples/langchain-python-rag-websummary/main.py | 4 ++-- examples/langchain-python-simple/README.md | 4 ++-- examples/langchain-python-simple/main.py | 2 +- examples/modelfile-mario/Modelfile | 2 +- examples/modelfile-mario/readme.md | 6 +++--- examples/python-dockerit/dockerit.py | 2 +- examples/python-json-datagenerator/predefinedschema.py | 2 +- examples/python-json-datagenerator/randomaddresses.py | 2 +- examples/python-json-datagenerator/readme.md | 4 ++-- examples/python-simplechat/client.py | 2 +- examples/python-simplechat/readme.md | 4 ++-- examples/typescript-simplechat/client.ts | 2 +- 19 files changed, 32 insertions(+), 24 deletions(-) delete mode 100644 examples/go-http-generate/README.md diff --git a/examples/go-chat/main.go b/examples/go-chat/main.go index 5266f03e9..7663fb8f4 100644 --- a/examples/go-chat/main.go +++ b/examples/go-chat/main.go @@ -35,7 +35,7 @@ func main() { ctx := context.Background() req := &api.ChatRequest{ - Model: "llama3", + Model: "llama3.1", Messages: messages, } diff --git a/examples/go-generate-streaming/main.go b/examples/go-generate-streaming/main.go index 494033511..3acfb22a9 100644 --- a/examples/go-generate-streaming/main.go +++ b/examples/go-generate-streaming/main.go @@ -16,7 +16,7 @@ func main() { // By default, GenerateRequest is streaming. req := &api.GenerateRequest{ - Model: "gemma", + Model: "gemma2", Prompt: "how many planets are there?", } diff --git a/examples/go-generate/main.go b/examples/go-generate/main.go index 50fbf64b7..2fe28742b 100644 --- a/examples/go-generate/main.go +++ b/examples/go-generate/main.go @@ -15,7 +15,7 @@ func main() { } req := &api.GenerateRequest{ - Model: "gemma", + Model: "gemma2", Prompt: "how many planets are there?", // set streaming to false diff --git a/examples/go-http-generate/README.md b/examples/go-http-generate/README.md deleted file mode 100644 index e69de29bb..000000000 diff --git a/examples/langchain-python-rag-document/README.md b/examples/langchain-python-rag-document/README.md index 20a73a883..e2f3bc028 100644 --- a/examples/langchain-python-rag-document/README.md +++ b/examples/langchain-python-rag-document/README.md @@ -4,6 +4,14 @@ This example provides an interface for asking questions to a PDF document. ## Setup +1. Ensure you have the `llama3.1` model installed: + +``` +ollama pull llama3.1 +``` + +2. Install the Python Requirements. + ``` pip install -r requirements.txt ``` diff --git a/examples/langchain-python-rag-document/main.py b/examples/langchain-python-rag-document/main.py index 3ed9499f2..6f7cec9be 100644 --- a/examples/langchain-python-rag-document/main.py +++ b/examples/langchain-python-rag-document/main.py @@ -51,7 +51,7 @@ while True: template=template, ) - llm = Ollama(model="llama3:8b", callback_manager=CallbackManager([StreamingStdOutCallbackHandler()])) + llm = Ollama(model="llama3.1", callback_manager=CallbackManager([StreamingStdOutCallbackHandler()])) qa_chain = RetrievalQA.from_chain_type( llm, retriever=vectorstore.as_retriever(), diff --git a/examples/langchain-python-rag-websummary/README.md b/examples/langchain-python-rag-websummary/README.md index 3f3b98733..29c706a39 100644 --- a/examples/langchain-python-rag-websummary/README.md +++ b/examples/langchain-python-rag-websummary/README.md @@ -4,10 +4,10 @@ This example summarizes the website, [https://ollama.com/blog/run-llama2-uncenso ## Running the Example -1. Ensure you have the `llama2` model installed: +1. Ensure you have the `llama3.1` model installed: ```bash - ollama pull llama2 + ollama pull llama3.1 ``` 2. Install the Python Requirements. diff --git a/examples/langchain-python-rag-websummary/main.py b/examples/langchain-python-rag-websummary/main.py index d1b05ba8a..77b09fbbc 100644 --- a/examples/langchain-python-rag-websummary/main.py +++ b/examples/langchain-python-rag-websummary/main.py @@ -5,8 +5,8 @@ from langchain.chains.summarize import load_summarize_chain loader = WebBaseLoader("https://ollama.com/blog/run-llama2-uncensored-locally") docs = loader.load() -llm = Ollama(model="llama3") +llm = Ollama(model="llama3.1") chain = load_summarize_chain(llm, chain_type="stuff") -result = chain.invoke(docs) +result = chain.invoke(docs) print(result) diff --git a/examples/langchain-python-simple/README.md b/examples/langchain-python-simple/README.md index d4102dec7..60db2c8c3 100644 --- a/examples/langchain-python-simple/README.md +++ b/examples/langchain-python-simple/README.md @@ -4,10 +4,10 @@ This example is a basic "hello world" of using LangChain with Ollama. ## Running the Example -1. Ensure you have the `llama3` model installed: +1. Ensure you have the `llama3.1` model installed: ```bash - ollama pull llama3 + ollama pull llama3.1 ``` 2. Install the Python Requirements. diff --git a/examples/langchain-python-simple/main.py b/examples/langchain-python-simple/main.py index 7cb652867..a7ed81d67 100644 --- a/examples/langchain-python-simple/main.py +++ b/examples/langchain-python-simple/main.py @@ -1,6 +1,6 @@ from langchain.llms import Ollama input = input("What is your question?") -llm = Ollama(model="llama3") +llm = Ollama(model="llama3.1") res = llm.predict(input) print (res) diff --git a/examples/modelfile-mario/Modelfile b/examples/modelfile-mario/Modelfile index 33d5952b1..a37470864 100644 --- a/examples/modelfile-mario/Modelfile +++ b/examples/modelfile-mario/Modelfile @@ -1,4 +1,4 @@ -FROM llama3 +FROM llama3.1 PARAMETER temperature 1 SYSTEM """ You are Mario from super mario bros, acting as an assistant. diff --git a/examples/modelfile-mario/readme.md b/examples/modelfile-mario/readme.md index e4f0d4172..c3f34197a 100644 --- a/examples/modelfile-mario/readme.md +++ b/examples/modelfile-mario/readme.md @@ -2,12 +2,12 @@ # Example character: Mario -This example shows how to create a basic character using Llama3 as the base model. +This example shows how to create a basic character using Llama3.1 as the base model. To run this example: 1. Download the Modelfile -2. `ollama pull llama3` to get the base model used in the model file. +2. `ollama pull llama3.1` to get the base model used in the model file. 3. `ollama create NAME -f ./Modelfile` 4. `ollama run NAME` @@ -18,7 +18,7 @@ Ask it some questions like "Who are you?" or "Is Peach in trouble again?" What the model file looks like: ``` -FROM llama3 +FROM llama3.1 PARAMETER temperature 1 SYSTEM """ You are Mario from Super Mario Bros, acting as an assistant. diff --git a/examples/python-dockerit/dockerit.py b/examples/python-dockerit/dockerit.py index b013102fa..6a288d906 100644 --- a/examples/python-dockerit/dockerit.py +++ b/examples/python-dockerit/dockerit.py @@ -4,7 +4,7 @@ imageName = input("Enter the name of the image: ") client = docker.from_env() s = requests.Session() output="" -with s.post('http://localhost:11434/api/generate', json={'model': 'dockerit', 'prompt': inputDescription}, stream=True) as r: +with s.post('http://localhost:11434/api/generate', json={'model': 'mattw/dockerit', 'prompt': inputDescription}, stream=True) as r: for line in r.iter_lines(): if line: j = json.loads(line) diff --git a/examples/python-json-datagenerator/predefinedschema.py b/examples/python-json-datagenerator/predefinedschema.py index 1fd54892b..68090ad79 100644 --- a/examples/python-json-datagenerator/predefinedschema.py +++ b/examples/python-json-datagenerator/predefinedschema.py @@ -2,7 +2,7 @@ import requests import json import random -model = "llama3" +model = "llama3.1" template = { "firstName": "", "lastName": "", diff --git a/examples/python-json-datagenerator/randomaddresses.py b/examples/python-json-datagenerator/randomaddresses.py index 72b1fefb9..878c98037 100644 --- a/examples/python-json-datagenerator/randomaddresses.py +++ b/examples/python-json-datagenerator/randomaddresses.py @@ -12,7 +12,7 @@ countries = [ "France", ] country = random.choice(countries) -model = "llama3" +model = "llama3.1" prompt = f"generate one realistically believable sample data set of a persons first name, last name, address in {country}, and phone number. Do not use common names. Respond using JSON. Key names should have no backslashes, values should use plain ascii with no special characters." diff --git a/examples/python-json-datagenerator/readme.md b/examples/python-json-datagenerator/readme.md index 883570443..5b444dff1 100644 --- a/examples/python-json-datagenerator/readme.md +++ b/examples/python-json-datagenerator/readme.md @@ -6,10 +6,10 @@ There are two python scripts in this example. `randomaddresses.py` generates ran ## Running the Example -1. Ensure you have the `llama3` model installed: +1. Ensure you have the `llama3.1` model installed: ```bash - ollama pull llama3 + ollama pull llama3.1 ``` 2. Install the Python Requirements. diff --git a/examples/python-simplechat/client.py b/examples/python-simplechat/client.py index f82a16b3e..85043d5f4 100644 --- a/examples/python-simplechat/client.py +++ b/examples/python-simplechat/client.py @@ -2,7 +2,7 @@ import json import requests # NOTE: ollama must be running for this to work, start the ollama app or run `ollama serve` -model = "llama3" # TODO: update this for whatever model you wish to use +model = "llama3.1" # TODO: update this for whatever model you wish to use def chat(messages): diff --git a/examples/python-simplechat/readme.md b/examples/python-simplechat/readme.md index dd2576bc1..4c2ded4d8 100644 --- a/examples/python-simplechat/readme.md +++ b/examples/python-simplechat/readme.md @@ -4,10 +4,10 @@ The **chat** endpoint is one of two ways to generate text from an LLM with Ollam ## Running the Example -1. Ensure you have the `llama3` model installed: +1. Ensure you have the `llama3.1` model installed: ```bash - ollama pull llama3 + ollama pull llama3.1 ``` 2. Install the Python Requirements. diff --git a/examples/typescript-simplechat/client.ts b/examples/typescript-simplechat/client.ts index a1e0eea37..8ad113b12 100644 --- a/examples/typescript-simplechat/client.ts +++ b/examples/typescript-simplechat/client.ts @@ -1,6 +1,6 @@ import * as readline from "readline"; -const model = "llama3"; +const model = "llama3.1"; type Message = { role: "assistant" | "user" | "system"; content: string; From 345420998e90090d2d6fba38ad5c2f3f5512adf4 Mon Sep 17 00:00:00 2001 From: Daniel Hiltgen Date: Mon, 22 Jul 2024 11:57:26 -0700 Subject: [PATCH 171/384] Prevent partial loading on mixed GPU brands In mult-brand GPU setups, if we couldn't fully load the model we would fall through the scheduler and mistakenly try to load across a mix of brands. This makes sure we find the set of GPU(s) that best fit for the partial load. --- server/sched.go | 31 +++++++++++++++++++++++++++---- server/sched_test.go | 39 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 66 insertions(+), 4 deletions(-) diff --git a/server/sched.go b/server/sched.go index 2daed3abb..92b8d508e 100644 --- a/server/sched.go +++ b/server/sched.go @@ -212,9 +212,12 @@ func (s *Scheduler) processPending(ctx context.Context) { } else if loadedCount == 0 { // No models loaded. Load the model but prefer the best fit. slog.Debug("loading first model", "model", pending.model.ModelPath) - g := pickBestFitGPUs(pending, ggml, gpus, &numParallel) + g := pickBestFullFitByLibrary(pending, ggml, gpus, &numParallel) if g != nil { gpus = g + } else { + // Only allow partial loads when this is the first model + gpus = pickBestPartialFitByLibrary(pending, ggml, gpus, &numParallel) } s.loadFn(pending, ggml, gpus, numParallel) break @@ -231,7 +234,7 @@ func (s *Scheduler) processPending(ctx context.Context) { // Update free memory from currently loaded models s.updateFreeSpace(availGpus) - fitGpus := pickBestFitGPUs(pending, ggml, availGpus, &numParallel) + fitGpus := pickBestFullFitByLibrary(pending, ggml, availGpus, &numParallel) if fitGpus != nil { slog.Debug("new model fits with existing models, loading") s.loadFn(pending, ggml, fitGpus, numParallel) @@ -668,11 +671,12 @@ func (a ByDuration) Less(i, j int) bool { // func (a BySize) Swap(i, j int) { a[i], a[j] = a[j], a[i] } // func (a BySize) Less(i, j int) bool { return a[i].estimatedVRAM < a[j].estimatedVRAM } -// pickBestFitGPUs will try to find the optimal placement of the model in the available GPUs where the model fully fits +// pickBestFullFitByLibrary will try to find the optimal placement of the model in the available GPUs where the model fully fits +// The list of GPUs returned will always be the same brand (library) // If the model can not be fit fully within the available GPU(s) nil is returned // If numParallel is <= 0, this will attempt try to optimize parallism based on available VRAM, and adjust // opts.NumCtx accordingly -func pickBestFitGPUs(req *LlmRequest, ggml *llm.GGML, gpus gpu.GpuInfoList, numParallel *int) gpu.GpuInfoList { +func pickBestFullFitByLibrary(req *LlmRequest, ggml *llm.GGML, gpus gpu.GpuInfoList, numParallel *int) gpu.GpuInfoList { var estimatedVRAM uint64 var numParallelToTry []int @@ -723,6 +727,25 @@ func pickBestFitGPUs(req *LlmRequest, ggml *llm.GGML, gpus gpu.GpuInfoList, numP return nil } +// If multiple Libraries are detected, pick the Library which loads the most layers for the model +func pickBestPartialFitByLibrary(req *LlmRequest, ggml *llm.GGML, gpus gpu.GpuInfoList, numParallel *int) gpu.GpuInfoList { + *numParallel = 1 + byLibrary := gpus.ByLibrary() + if len(byLibrary) <= 1 { + return gpus + } + var bestEstimate uint64 + var bestFit int + for i, gl := range byLibrary { + _, estimatedVRAM := llm.PredictServerFit(gl, ggml, req.model.AdapterPaths, req.model.ProjectorPaths, req.opts) + if estimatedVRAM > bestEstimate { + bestEstimate = estimatedVRAM + bestFit = i + } + } + return byLibrary[bestFit] +} + // findRunnerToUnload finds a runner to unload to make room for a new model func (s *Scheduler) findRunnerToUnload() *runnerRef { s.loadedMu.Lock() diff --git a/server/sched_test.go b/server/sched_test.go index 9ddd1fabe..a186ce0e1 100644 --- a/server/sched_test.go +++ b/server/sched_test.go @@ -666,6 +666,45 @@ func TestAlreadyCanceled(t *testing.T) { require.Empty(t, scenario1a.req.successCh) } +func TestHomogeneousGPUs(t *testing.T) { + ctx, done := context.WithTimeout(context.Background(), 100*time.Millisecond) + defer done() + s := InitScheduler(ctx) + + s.getGpuFn = func() gpu.GpuInfoList { + // Set memory values to require the model to be spread + gpus := []gpu.GpuInfo{ + {Library: "cuda"}, + {Library: "rocm"}, + } + gpus[0].TotalMemory = 1 * format.GibiByte + gpus[0].FreeMemory = 256 * format.MebiByte + gpus[1].TotalMemory = 1 * format.GibiByte + gpus[1].FreeMemory = 256 * format.MebiByte + return gpus + } + s.getCpuFn = getCpuFn + a := newScenarioRequest(t, ctx, "ollama-model-1", 10, &api.Duration{Duration: 5 * time.Millisecond}) + s.newServerFn = func(gpus gpu.GpuInfoList, model string, ggml *llm.GGML, adapters []string, projectors []string, opts api.Options, numParallel int) (llm.LlamaServer, error) { + require.Len(t, gpus, 1) + return a.newServer(gpus, model, ggml, adapters, projectors, opts, numParallel) + } + slog.Info("a") + s.pendingReqCh <- a.req + require.Len(t, s.pendingReqCh, 1) + s.Run(ctx) + select { + case resp := <-a.req.successCh: + require.Equal(t, resp.llama, a.srv) + require.Empty(t, s.pendingReqCh) + require.Empty(t, a.req.errCh) + case err := <-a.req.errCh: + t.Fatal(err.Error()) + case <-ctx.Done(): + t.Fatal("timeout") + } +} + type mockLlm struct { pingResp error waitResp error From 1b44d873e74f62de4f53f154da386919c1426f8b Mon Sep 17 00:00:00 2001 From: royjhan <65097070+royjhan@users.noreply.github.com> Date: Tue, 30 Jul 2024 13:12:21 -0700 Subject: [PATCH 172/384] Add Metrics to `api\embed` response (#5709) * add prompt tokens to embed response * rm slog * metrics * types * prompt n * clean up * reset submodule * update tests * test name * list metrics --- api/types.go | 4 ++++ integration/embed_test.go | 8 ++++++++ llm/ext_server/server.cpp | 7 ++++++- llm/server.go | 13 +++++++------ server/routes.go | 18 ++++++++++++------ server/sched_test.go | 4 ++-- 6 files changed, 39 insertions(+), 15 deletions(-) diff --git a/api/types.go b/api/types.go index ea5161ff6..c25296521 100644 --- a/api/types.go +++ b/api/types.go @@ -267,6 +267,10 @@ type EmbedRequest struct { type EmbedResponse struct { Model string `json:"model"` Embeddings [][]float32 `json:"embeddings"` + + TotalDuration time.Duration `json:"total_duration,omitempty"` + LoadDuration time.Duration `json:"load_duration,omitempty"` + PromptEvalCount int `json:"prompt_eval_count,omitempty"` } // EmbeddingRequest is the request passed to [Client.Embeddings]. diff --git a/integration/embed_test.go b/integration/embed_test.go index 61b36fa29..10333d5df 100644 --- a/integration/embed_test.go +++ b/integration/embed_test.go @@ -69,6 +69,10 @@ func TestAllMiniLMEmbed(t *testing.T) { if !floatsEqual32(res.Embeddings[0][0], 0.010071031) { t.Fatalf("expected 0.010071031, got %.8f", res.Embeddings[0][0]) } + + if res.PromptEvalCount != 8 { + t.Fatalf("expected 8 prompt tokens, got %d", res.PromptEvalCount) + } } func TestAllMiniLMBatchEmbed(t *testing.T) { @@ -97,6 +101,10 @@ func TestAllMiniLMBatchEmbed(t *testing.T) { if !floatsEqual32(res.Embeddings[0][0], 0.010071031) || !floatsEqual32(res.Embeddings[1][0], -0.009802706) { t.Fatalf("expected 0.010071031 and -0.009802706, got %.8f and %.8f", res.Embeddings[0][0], res.Embeddings[1][0]) } + + if res.PromptEvalCount != 16 { + t.Fatalf("expected 16 prompt tokens, got %d", res.PromptEvalCount) + } } func TestAllMiniLMEmbedTruncate(t *testing.T) { diff --git a/llm/ext_server/server.cpp b/llm/ext_server/server.cpp index 0d51460c7..d72bb1b14 100644 --- a/llm/ext_server/server.cpp +++ b/llm/ext_server/server.cpp @@ -1221,6 +1221,7 @@ struct llama_server_context res.result_json = json { {"embedding", std::vector(embd, embd + n_embd)}, + {"timings", slot.get_formated_timings()}, }; } } @@ -3203,11 +3204,15 @@ int main(int argc, char **argv) { responses = result.result_json.value("results", std::vector{result.result_json}); json embeddings = json::array(); + + int prompt_n = 0; for (auto & elem : responses) { embeddings.push_back(elem.at("embedding")); + prompt_n += elem.at("timings").at("prompt_n").get(); } + // send the result - json embedding_res = json{{"embedding", embeddings}}; + json embedding_res = json{{"embedding", embeddings}, {"prompt_n", prompt_n}}; return res.set_content(embedding_res.dump(), "application/json; charset=utf-8"); } }); diff --git a/llm/server.go b/llm/server.go index 8127960f0..afde077e7 100644 --- a/llm/server.go +++ b/llm/server.go @@ -33,7 +33,7 @@ type LlamaServer interface { Ping(ctx context.Context) error WaitUntilRunning(ctx context.Context) error Completion(ctx context.Context, req CompletionRequest, fn func(CompletionResponse)) error - Embed(ctx context.Context, input []string) ([][]float32, error) + Embed(ctx context.Context, input []string) (*EmbedResponse, error) Tokenize(ctx context.Context, content string) ([]int, error) Detokenize(ctx context.Context, tokens []int) (string, error) Close() error @@ -879,10 +879,11 @@ type EmbedRequest struct { } type EmbedResponse struct { - Embedding [][]float32 `json:"embedding"` + Embedding [][]float32 `json:"embedding"` + PromptEvalCount int `json:"prompt_n"` } -func (s *llmServer) Embed(ctx context.Context, input []string) ([][]float32, error) { +func (s *llmServer) Embed(ctx context.Context, input []string) (*EmbedResponse, error) { if err := s.sem.Acquire(ctx, 1); err != nil { slog.Error("Failed to acquire semaphore", "error", err) return nil, err @@ -924,12 +925,12 @@ func (s *llmServer) Embed(ctx context.Context, input []string) ([][]float32, err return nil, fmt.Errorf("%s", body) } - var embedding EmbedResponse - if err := json.Unmarshal(body, &embedding); err != nil { + var e EmbedResponse + if err := json.Unmarshal(body, &e); err != nil { return nil, fmt.Errorf("unmarshal tokenize response: %w", err) } - return embedding.Embedding, nil + return &e, nil } type TokenizeRequest struct { diff --git a/server/routes.go b/server/routes.go index e6ffe5268..a560f3694 100644 --- a/server/routes.go +++ b/server/routes.go @@ -284,6 +284,7 @@ func (s *Server) GenerateHandler(c *gin.Context) { } func (s *Server) EmbedHandler(c *gin.Context) { + checkpointStart := time.Now() var req api.EmbedRequest err := c.ShouldBindJSON(&req) switch { @@ -332,6 +333,8 @@ func (s *Server) EmbedHandler(c *gin.Context) { return } + checkpointLoaded := time.Now() + kvData, err := getKVData(m.ModelPath, false) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) @@ -370,13 +373,16 @@ func (s *Server) EmbedHandler(c *gin.Context) { return } - for i, e := range embeddings { - embeddings[i] = normalize(e) + for i, e := range embeddings.Embedding { + embeddings.Embedding[i] = normalize(e) } resp := api.EmbedResponse{ - Model: req.Model, - Embeddings: embeddings, + Model: req.Model, + Embeddings: embeddings.Embedding, + TotalDuration: time.Since(checkpointStart), + LoadDuration: checkpointLoaded.Sub(checkpointStart), + PromptEvalCount: embeddings.PromptEvalCount, } c.JSON(http.StatusOK, resp) } @@ -428,9 +434,9 @@ func (s *Server) EmbeddingsHandler(c *gin.Context) { return } - embedding := make([]float64, len(embeddings[0])) + embedding := make([]float64, len(embeddings.Embedding[0])) - for i, v := range embeddings[0] { + for i, v := range embeddings.Embedding[0] { embedding[i] = float64(v) } diff --git a/server/sched_test.go b/server/sched_test.go index a186ce0e1..4f8789fa8 100644 --- a/server/sched_test.go +++ b/server/sched_test.go @@ -709,7 +709,7 @@ type mockLlm struct { pingResp error waitResp error completionResp error - embedResp [][]float32 + embedResp *llm.EmbedResponse embedRespErr error tokenizeResp []int tokenizeRespErr error @@ -727,7 +727,7 @@ func (s *mockLlm) WaitUntilRunning(ctx context.Context) error { return s.waitRes func (s *mockLlm) Completion(ctx context.Context, req llm.CompletionRequest, fn func(llm.CompletionResponse)) error { return s.completionResp } -func (s *mockLlm) Embed(ctx context.Context, input []string) ([][]float32, error) { +func (s *mockLlm) Embed(ctx context.Context, input []string) (*llm.EmbedResponse, error) { return s.embedResp, s.embedRespErr } func (s *mockLlm) Tokenize(ctx context.Context, content string) ([]int, error) { From afa8d6e9d56da834a03df7817d065f6c8b46e102 Mon Sep 17 00:00:00 2001 From: jmorganca Date: Tue, 30 Jul 2024 18:06:26 -0700 Subject: [PATCH 173/384] patch gemma support --- llm/patches/10-params.diff | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 llm/patches/10-params.diff diff --git a/llm/patches/10-params.diff b/llm/patches/10-params.diff new file mode 100644 index 000000000..56699b8ec --- /dev/null +++ b/llm/patches/10-params.diff @@ -0,0 +1,20 @@ +diff --git a/src/llama.cpp b/src/llama.cpp +index a207451f..fba6b175 100644 +--- a/src/llama.cpp ++++ b/src/llama.cpp +@@ -4969,6 +4969,7 @@ static void llm_load_hparams( + hparams.attn_soft_cap = true; + + switch (hparams.n_layer) { ++ case 26: model.type = e_model::MODEL_2B; break; + case 42: model.type = e_model::MODEL_9B; break; + case 46: model.type = e_model::MODEL_27B; break; + default: model.type = e_model::MODEL_UNKNOWN; +@@ -11736,6 +11737,7 @@ struct llm_build_context { + + // ref: https://github.com/google/gemma_pytorch/commit/03e657582d17cb5a8617ebf333c1c16f3694670e + switch (model.type) { ++ case e_model::MODEL_2B: Qcur = ggml_scale(ctx0, Qcur, 1.0f / sqrtf(float(n_embd_head_k))); break; + case e_model::MODEL_9B: Qcur = ggml_scale(ctx0, Qcur, 1.0f / sqrtf(float(n_embd_head_k))); break; + case e_model::MODEL_27B: Qcur = ggml_scale(ctx0, Qcur, 1.0f / sqrtf(float(n_embd / n_head))); break; + default: GGML_ABORT("fatal error"); From 5d6657835669064fa9658e6712b01887a072c606 Mon Sep 17 00:00:00 2001 From: Jeffrey Morgan Date: Tue, 30 Jul 2024 18:08:34 -0700 Subject: [PATCH 174/384] Update README.md Better example for multi-modal input --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 824b3761a..0593a7851 100644 --- a/README.md +++ b/README.md @@ -173,7 +173,7 @@ I'm a basic program that prints the famous "Hello, world!" message to the consol ### Multimodal models ``` ->>> What's in this image? /Users/jmorgan/Desktop/smile.png +ollama run llava "What's in this image? /Users/jmorgan/Desktop/smile.png" The image features a yellow smiley face, which is likely the central focus of the picture. ``` From 3579b4966a9b21e048db4f7610e3f9f4a5c4dc64 Mon Sep 17 00:00:00 2001 From: Michael Date: Tue, 30 Jul 2024 18:40:09 -0700 Subject: [PATCH 175/384] Update README to include Firebase Genkit (#6083) Firebase Genkit --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 0593a7851..941a4f995 100644 --- a/README.md +++ b/README.md @@ -337,6 +337,7 @@ See the [API documentation](./docs/api.md) for all endpoints. ### Libraries - [LangChain](https://python.langchain.com/docs/integrations/llms/ollama) and [LangChain.js](https://js.langchain.com/docs/modules/model_io/models/llms/integrations/ollama) with [example](https://js.langchain.com/docs/use_cases/question_answering/local_retrieval_qa) +- [Firebase Genkit](https://firebase.google.com/docs/genkit/plugins/ollama) - [LangChainGo](https://github.com/tmc/langchaingo/) with [example](https://github.com/tmc/langchaingo/tree/main/examples/ollama-completion-example) - [LangChain4j](https://github.com/langchain4j/langchain4j) with [example](https://github.com/langchain4j/langchain4j-examples/tree/main/ollama-examples/src/main/java) - [LangChainRust](https://github.com/Abraxas-365/langchain-rust) with [example](https://github.com/Abraxas-365/langchain-rust/blob/main/examples/llm_ollama.rs) From 463a8aa2731a9fe5258c6c7e1466f3dae27f0c6a Mon Sep 17 00:00:00 2001 From: Jeffrey Morgan Date: Tue, 30 Jul 2024 21:01:12 -0700 Subject: [PATCH 176/384] Create SECURITY.md --- SECURITY.md | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 SECURITY.md diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 000000000..d38bb7c4e --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,25 @@ +# Security + +The Ollama maintainer team takes security seriously and will actively work to resolve security issues. + +## Reporting a vulnerability + +If you discover a security vulnerability, please do not open a public issue. Instead, please report it by emailing hello@ollama.com. We ask that you give us sufficient time to investigate and address the vulnerability before disclosing it publicly. + +Please include the following details in your report: +- A description of the vulnerability +- Steps to reproduce the issue +- Your assessment of the potential impact +- Any possible mitigations + +## Security best practices + +While the maintainer team does their best to secure Ollama, users are encouraged to implement their own security best practices, such as: + +- Regularly updating to the latest version of Ollama +- Securing access to hosted instances of Ollama +- Monitoring systems for unusual activity + +## Contact + +For any other questions or concerns related to security, please contact us at hello@ollama.com From 71399aa682726e472ca271f02417d87f6f8be429 Mon Sep 17 00:00:00 2001 From: Daniel Nguyen Date: Wed, 31 Jul 2024 22:44:58 +0700 Subject: [PATCH 177/384] Added BoltAI as a desktop UI for Ollama (#6096) --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 941a4f995..0cc152662 100644 --- a/README.md +++ b/README.md @@ -299,6 +299,7 @@ See the [API documentation](./docs/api.md) for all endpoints. - [AI Studio](https://github.com/MindWorkAI/AI-Studio) - [Sidellama](https://github.com/gyopak/sidellama) (browser-based LLM client) - [LLMStack](https://github.com/trypromptly/LLMStack) (No-code multi-agent framework to build LLM agents and workflows) +- [BoltAI for Mac](https://boltai.com) (AI Chat Client for Mac) ### Terminal From 6b252918fb5e17f9be5975efe1681a92153b8379 Mon Sep 17 00:00:00 2001 From: Michael Yang Date: Mon, 3 Jun 2024 09:49:13 -0700 Subject: [PATCH 178/384] update convert test to check result data --- convert/convert_test.go | 111 +++++-- .../testdata/Meta-Llama-3-8B-Instruct.json | 313 ++++++++++++++++++ .../testdata/Mistral-7B-Instruct-v0.2.json | 313 ++++++++++++++++++ .../testdata/Mixtral-8x7B-Instruct-v0.1.json | 1 + convert/testdata/gemma-2b-it.json | 188 +++++++++++ llm/ggla.go | 14 +- llm/ggml.go | 7 +- llm/gguf.go | 14 +- 8 files changed, 924 insertions(+), 37 deletions(-) create mode 100644 convert/testdata/Meta-Llama-3-8B-Instruct.json create mode 100644 convert/testdata/Mistral-7B-Instruct-v0.2.json create mode 100644 convert/testdata/Mixtral-8x7B-Instruct-v0.1.json create mode 100644 convert/testdata/gemma-2b-it.json diff --git a/convert/convert_test.go b/convert/convert_test.go index 6aa33a49c..a3727bedc 100644 --- a/convert/convert_test.go +++ b/convert/convert_test.go @@ -1,29 +1,36 @@ -//go:build slow - package convert import ( + "crypto/sha256" + "encoding/json" + "flag" + "fmt" + "io" + "log/slog" + "math" "os" "path/filepath" + "slices" "testing" "github.com/ollama/ollama/llm" + "golang.org/x/exp/maps" ) -func convertFull(t *testing.T, p string) (llm.KV, llm.Tensors) { +func convertFull(t *testing.T, d string) (*os.File, llm.KV, llm.Tensors) { t.Helper() - mf, err := GetModelFormat(p) + mf, err := GetModelFormat(d) if err != nil { t.Fatal(err) } - params, err := mf.GetParams(p) + params, err := mf.GetParams(d) if err != nil { t.Fatal(err) } - arch, err := mf.GetModelArch("", p, params) + arch, err := mf.GetModelArch("", d, params) if err != nil { t.Fatal(err) } @@ -50,53 +57,91 @@ func convertFull(t *testing.T, p string) (llm.KV, llm.Tensors) { if err != nil { t.Fatal(err) } - defer r.Close() + t.Cleanup(func() { r.Close() }) - m, _, err := llm.DecodeGGML(r) + m, _, err := llm.DecodeGGML(r, math.MaxInt) if err != nil { t.Fatal(err) } - return m.KV(), m.Tensors() + if _, err := r.Seek(0, io.SeekStart); err != nil { + t.Fatal(err) + } + + return r, m.KV(), m.Tensors() +} + +func TestMain(m *testing.M) { + var level slog.Level + flag.TextVar(&level, "level", slog.LevelInfo, "log level") + flag.Parse() + slog.SetLogLoggerLevel(level) + os.Exit(m.Run()) } func TestConvertFull(t *testing.T) { - cases := []struct { - path string - arch string - tensors int - layers int - }{ - {"Meta-Llama-3-8B-Instruct", "llama", 291, 35}, - {"Mistral-7B-Instruct-v0.2", "llama", 291, 35}, - {"Mixtral-8x7B-Instruct-v0.1", "llama", 291, 35}, - {"gemma-2b-it", "gemma", 164, 20}, + cases := []string{ + "Meta-Llama-3-8B-Instruct", + "Mistral-7B-Instruct-v0.2", + "Mixtral-8x7B-Instruct-v0.1", + "gemma-2b-it", } - for _, tt := range cases { - t.Run(tt.path, func(t *testing.T) { - p := filepath.Join("testdata", tt.path) - if _, err := os.Stat(p); err != nil { + for i := range cases { + tt := cases[i] + t.Run(tt, func(t *testing.T) { + t.Parallel() + + p := filepath.Join("testdata", tt) + if testing.Short() { + t.Skip("skipping in short mode") + } else if _, err := os.Stat(p); err != nil { t.Skipf("%s not found", p) } - kv, tensors := convertFull(t, p) + f, kv, tensors := convertFull(t, p) + actual := make(map[string]string) + for k, v := range kv { + if s, ok := v.(json.Marshaler); !ok { + actual[k] = fmt.Sprintf("%v", v) + } else { + bts, err := json.Marshal(s) + if err != nil { + t.Fatal(err) + } - if kv.Architecture() != tt.arch { - t.Fatalf("expected llama, got %s", kv.Architecture()) + actual[k] = fmt.Sprintf("%x", sha256.Sum256(bts)) + } } - if kv.FileType().String() != "F16" { - t.Fatalf("expected F16, got %s", kv.FileType()) + for _, tensor := range tensors.Items { + sha256sum := sha256.New() + sr := io.NewSectionReader(f, int64(tensors.Offset+tensor.Offset), int64(tensor.Size())) + if _, err := io.Copy(sha256sum, sr); err != nil { + t.Fatal(err) + } + + actual[tensor.Name] = fmt.Sprintf("%x", sha256sum.Sum(nil)) } - if len(tensors) != tt.tensors { - t.Fatalf("expected %d tensors, got %d", tt.tensors, len(tensors)) + expectFile, err := os.Open(filepath.Join("testdata", fmt.Sprintf("%s.json", tt))) + if err != nil { + t.Fatal(err) } - layers := tensors.Layers() - if len(layers) != tt.layers { - t.Fatalf("expected %d layers, got %d", tt.layers, len(layers)) + var expect map[string]string + if err := json.NewDecoder(expectFile).Decode(&expect); err != nil { + t.Fatal(err) + } + + keys := maps.Keys(expect) + slices.Sort(keys) + for _, k := range keys { + if v, ok := actual[k]; !ok { + t.Errorf("missing %s", k) + } else if v != expect[k] { + t.Errorf("unexpected %s: want %s, got %s", k, expect[k], v) + } } }) } diff --git a/convert/testdata/Meta-Llama-3-8B-Instruct.json b/convert/testdata/Meta-Llama-3-8B-Instruct.json new file mode 100644 index 000000000..808826bb6 --- /dev/null +++ b/convert/testdata/Meta-Llama-3-8B-Instruct.json @@ -0,0 +1,313 @@ +{ + "general.architecture": "llama", + "general.file_type": "1", + "general.quantization_version": "2", + "llama.block_count": "32", + "llama.context_length": "8192", + "llama.embedding_length": "4096", + "llama.feed_forward_length": "14336", + "llama.rope.dimension_count": "128", + "llama.rope.freq_base": "500000", + "llama.vocab_size": "128256", + "llama.attention.head_count": "32", + "llama.attention.head_count_kv": "8", + "llama.attention.layer_norm_rms_epsilon": "1e-05", + "tokenizer.ggml.model": "gpt2", + "tokenizer.ggml.pre": "llama-bpe", + "tokenizer.ggml.bos_token_id": "128000", + "tokenizer.ggml.eos_token_id": "128009", + "tokenizer.ggml.merges": "d0cbac1fcc9dcf03724b8db5c9bfb593ae1cf68fb9bc72eb1d15274dcbbf618b", + "tokenizer.ggml.token_type": "d70a88809fd7da6f1f028622685cd64268a7a922c5d343c96f25b66327358978", + "tokenizer.ggml.tokens": "765b529dbcbc42dd202ce657341c63807b51f3b07e09898f6aa6196326865d5a", + "token_embd.weight": "b53102a11d9064bbd404833e3464b1b13e08ce73300b442312cccde2f19b2698", + "blk.0.attn_norm.weight": "7318df3cca9e8d153ff0a503026a1265e63d20b2a8c1dd7a2769585082b5d1ee", + "blk.0.ffn_down.weight": "b950806a1fc722c9fad7fd0b20c3c0a7fb50f14395e1e7663a590bfd62e20900", + "blk.0.ffn_gate.weight": "e73e580af6d4f08e060a74a3c25efdf5d3bed99e183d95a5a85ae859014839fd", + "blk.0.ffn_up.weight": "c8158af679ef99746da1befb67eebb19489e0bbe6ce7d97e13e348508244e516", + "blk.0.ffn_norm.weight": "7ec69c3c31e95e49a3359003b0033f6b9e85561a3e3fd83e7476661ecdd756bb", + "blk.0.attn_k.weight": "2732303257bac969b4964e0e32ec08b5a7f5c031bb02bf6ac4467b3ea0ebcf1e", + "blk.0.attn_output.weight": "ecda1d43b4ccc91cd5b366d7e7a275353990ac78561a07c83d9c77031aba12dc", + "blk.0.attn_q.weight": "569b1f5faf92b6f00910cf7effb2d5862f91038ce5c3b0019fc10e5d79fbd5e1", + "blk.0.attn_v.weight": "aa8416c5ef7e32fb54a1f20d6ac651656845d4af240564b397c39bd83e06e3b8", + "blk.1.attn_norm.weight": "03327e02862908c2a44b2f52decdb924bf4201f400b46f8037a9cb2e1d7a61ff", + "blk.1.ffn_down.weight": "5a83a87603f38c99f8e1e370a2d5f967bb45ac51d881a609304a7811027321e0", + "blk.1.ffn_gate.weight": "31da0572c79e655186c721c231376f85e56cdcc6257c28d08c8c5b40d5c22b40", + "blk.1.ffn_up.weight": "e0c811d64ca155c8de10a868e72015d43888834804614ee1aa2953129ffbc90f", + "blk.1.ffn_norm.weight": "5861f313d6137d6f0f904d423df47fffc6069e224ff746e1b637ac9c7f0af862", + "blk.1.attn_k.weight": "5fbbec0acca6457b9416ebdcd90e526885d0224537b7628f6be376a7f275313d", + "blk.1.attn_output.weight": "b237c9763fa3f75166a6f70b70f1566e77d0d89dfa164ed1b3137393e90575c3", + "blk.1.attn_q.weight": "c0a9cf4a98b4882b16f3eb2b49d933793dcc5357abb246fd3fe3134ed2b12e1c", + "blk.1.attn_v.weight": "96867111727200cac1af7865189dd41fd62b47584e5e5f33a91f1d34509cbd40", + "blk.2.attn_norm.weight": "f392f8a88ee3a95b1cc19c40dd4ef66317037b0faaa1800f610779e129ee0539", + "blk.2.ffn_down.weight": "73823eef46632aedcc8c1cb08a736b6aa97ca97842cd1fdfc5567d8dec459662", + "blk.2.ffn_gate.weight": "f4909ae19fc3848b00bb8b9050122e74f8e903b89e22937036f4cc9fea20a718", + "blk.2.ffn_up.weight": "16f4904a3d814ea68f00519724fc4943e48444a84c786bda39aa5efc298a7d84", + "blk.2.ffn_norm.weight": "e3ccdf56e75cb969f6f69c39caf6daf7c4e70e89e25df0f4d2e4bc60e159aafe", + "blk.2.attn_k.weight": "c3beb1e0a11bcf007ef0f0d8f6bdd3082d8b29090cd29597846b5d51e308a8e5", + "blk.2.attn_output.weight": "bb9f66c32cff51154fea92933c2cd62549236f8cb1a767f9ef28d3f99809b343", + "blk.2.attn_q.weight": "8eba394132eef2a05c5a92d62d2376000f7948448d7a2dc74e6b608203add20d", + "blk.2.attn_v.weight": "88f61f77c53567c617db3eef8f30621109a750e679f6784f7911739bd42c2f02", + "blk.3.attn_norm.weight": "7b996675b7ca75fa24107b3ebe0788653ede0f49ac83b8659d71ff54d591f81a", + "blk.3.ffn_down.weight": "2cb332bc05e4821962fdc9dcbcc7cc12630f32117711b687d18fb53c0bc4fbf4", + "blk.3.ffn_gate.weight": "340b387c7f208c8f0a6db904ef8d87c1e84b7d6ad57177abd32d86c8d18b760f", + "blk.3.ffn_up.weight": "07484433f8a7ee061c55aa0de2ecc009f769b0617c9c0ec096e9bb2946df9f0e", + "blk.3.ffn_norm.weight": "4f1a4ade36b393af341240bc894a2aab09cff7e4d56dc4658445deb107f9371b", + "blk.3.attn_k.weight": "483dcd96acb4528df84b9842970994630dbd82b8715ace394aa8b39fcf8d6291", + "blk.3.attn_output.weight": "beaff0810687923585642ee11d929cbf3b43dc6f87f30ddb552c222ab57bdbb3", + "blk.3.attn_q.weight": "0739355002f6fce520863add697e0ff25fc88215322dc3f993be7bb68dcce7e8", + "blk.3.attn_v.weight": "c216d17b6d90ee3e07f82598b8161fae34de2f392dbb0f745b682b578c324767", + "blk.4.attn_norm.weight": "91ab405bc4ba15bf63af233f266aa43aaab43789a9e6596e14a357c2ac7df217", + "blk.4.ffn_down.weight": "620f34ee75cdc73aecb8949af5fbb0d2437fd81422b6d8eb7acfc52addb9fc68", + "blk.4.ffn_gate.weight": "f6feec7bc9acadf35ec22532f8998d8e50f31afedabb19263590dcf8b9a92eee", + "blk.4.ffn_up.weight": "4a72af7cd28fd07b038f6cc4406678d120517280236ea85d9e76eff40ab2cc22", + "blk.4.ffn_norm.weight": "1805b37b44d5d682bdbd2fadeafb763ee001617d7870848cc487079ee34b21f9", + "blk.4.attn_k.weight": "a1e4f9d97cdf4c1b0d177cf00c4e32d1be30c1984a239b3c9bd73f8848888853", + "blk.4.attn_output.weight": "a1547e2497c423b0aff0eee71d9300d6fdf4e4986679418b6e637b69a9a6720b", + "blk.4.attn_q.weight": "0677483a9264ea6803d03d304d87a54632242cb516e8b76b6e3e8284c2f4de04", + "blk.4.attn_v.weight": "02691ba3af344fcc1969428ab0df811ac94aaa2fd91b0dc4ec1ac0a58806980d", + "blk.5.attn_norm.weight": "ba9c028335e5c895b87a5bd1448ca429248f9746ed97bdcb8679923206117156", + "blk.5.ffn_down.weight": "ccfdc9006acad1940a6bc05042a3947f1066acd671e0bb53b7684e9eea9ef5c9", + "blk.5.ffn_gate.weight": "623157679f1e742ccc3807c0b0153ddc8450104de75ec62f1370ec3807c09cf4", + "blk.5.ffn_up.weight": "05748804c65091f963729b58b085f58351891cac8a2861f5eae26b06aa60b2a0", + "blk.5.ffn_norm.weight": "84bae55af2efc8b8429f09056c8c04990c466dae31cb3f9356038b8957f1b406", + "blk.5.attn_k.weight": "8c766180c726b037d587fc52371de6e3307140c52409011609d1225624b6a3eb", + "blk.5.attn_output.weight": "490b582b3b1dc151ae55aee8b6743dad6c01fb49e43afefb6e68394b74be3d73", + "blk.5.attn_q.weight": "6f7b8ca4d9025ec836a44bbcca46be30c66b471a9fb62943ddff8288b3731409", + "blk.5.attn_v.weight": "9f70df3ba00c9e723214b3da83ff435a2163fff5915f75515c9664c05c866c27", + "blk.6.attn_norm.weight": "1a4a66613a682df6f061fc7c4d986f9f7e9175b62f0c42fc1ef31db536bd5942", + "blk.6.ffn_down.weight": "c56f25e4e49b443dbc82d88311ee63bc1f5002cc67e52f4787fd5f003aedeac1", + "blk.6.ffn_gate.weight": "31a5cf1aa9b831a81588d508550f51fc425f9517c43254d4ef7096d38029cf04", + "blk.6.ffn_up.weight": "ce135f3a1163e0c9297a615bdbe68a67ead21edce8debbfa9f6e15e6af8d4c94", + "blk.6.ffn_norm.weight": "4e328ce0648c94e732bc40501858ef6262ad1161e2e407b0cdcf4813fa9d45d8", + "blk.6.attn_k.weight": "1eb1c4c9f9c4c7ff7f5429075e0dc6a7782bed55109fa88df209a817dd8ef960", + "blk.6.attn_output.weight": "3d32986b56873b88655ee1edabdd413fdd9ab18b82108c9ce90bdbc2d3a6f3a3", + "blk.6.attn_q.weight": "8432f583b3a2809c99c393f9beb077cb0534dd5d247c17108f2986cadc6651f6", + "blk.6.attn_v.weight": "5045381513815bb91839dbac8335ffe49bbc7b0008369de7ea97eb676c5e2b36", + "blk.7.attn_norm.weight": "3dabd003638ec2499bfc8a48c49eef34276caab4fe76894eb963207848c2fdaf", + "blk.7.ffn_down.weight": "194fae858608bdcffd235be59ab119d0b91c8549f864ea06dae69249e099935f", + "blk.7.ffn_gate.weight": "00b24c29c30246892bce0791be804a89701d4c1332777e0bcdad5d9d5666604f", + "blk.7.ffn_up.weight": "44d7082a5280080c90cef9e19d410391de34f212ca0736377769b8ddd0c82d5e", + "blk.7.ffn_norm.weight": "21fe8a7fd6911c64e0d15a788b3b4cb6d71dd6ec51de65f760ee89afbb6ae53e", + "blk.7.attn_k.weight": "57a149eec5f6744a9526cd3925ac073f9d12db0fbcb5afe042ef4dc846458c44", + "blk.7.attn_output.weight": "0e9c28a3e81a2880251ce5eed77bcb8be8aaa1a51c9cb6de820b47ed83849fc2", + "blk.7.attn_q.weight": "15ee75263ee4e2a43eb322bc159ae004bb7d77e3a7e63ee4ddab700430693fff", + "blk.7.attn_v.weight": "440aa970bba4bff429fd7b7b1de21f2ad14fb2952b776cfa4acee68d7c6e9b8f", + "blk.8.attn_norm.weight": "af5b44825633c42c1ae964c82bb2be6a242d3a751f0a91f1bae4f593e8f5b6ec", + "blk.8.ffn_down.weight": "b11c14c76adca94fa200496dd2c10743becb23aab6642443ef1ae6d8710edbc1", + "blk.8.ffn_gate.weight": "7bb03d3325bf8637ae2fa1296b0651356515578d46a7c5ca65c7a923d7de27bc", + "blk.8.ffn_up.weight": "b956ef0a0669b5a9c9bf3a8da2d1c24f52d331cfb7354f6d7c51bd65be355e30", + "blk.8.ffn_norm.weight": "c78c3d748302edfef76f71ea5cb2055c94352122eee8b9b1173779a1814d224e", + "blk.8.attn_k.weight": "c0fba6a596ed9c1c32a7055c31a935a8b31e42b77282ee47c1f03ee3bde736b5", + "blk.8.attn_output.weight": "83cf9947080c5d8d571f04a842bc3dcfe7bbb0195fb25b346e22635e8649f2d4", + "blk.8.attn_q.weight": "47409350a576b333d97b7c877d69f47f46df504f3765102dfc0be9e521c7ecd6", + "blk.8.attn_v.weight": "1999dff91404fdcf1ecb34d9eaaaa9244ec7658a74dec8feb7cfd1fddba0347e", + "blk.9.attn_norm.weight": "1e6e29d5c3889ab4e1b0a5b9998cba60179b0f1fca133515df49cbc19d092593", + "blk.9.ffn_down.weight": "acb898a6490adff592e10b4c62d70edc5941661ee6da44658500e9205357c8e9", + "blk.9.ffn_gate.weight": "4cff63013593aadc3ffbaaa6ed70ffdba1224cd43c3644bf6f4162b5ac1ab542", + "blk.9.ffn_up.weight": "f985b5a2d6cf4fe32c7256301c3c89b8ad22b59e516342c52da42d8110766a4e", + "blk.9.ffn_norm.weight": "0d659c538bc6b21ed0018f107ab674a7424a00a42946c80e07208b479b21918f", + "blk.9.attn_k.weight": "f67611d888780d1b38c1c146b361c65310c8183bdf64fd73e2259985c6e8517f", + "blk.9.attn_output.weight": "f12ca1fa62a02ddc3f77f798bfb5707e0c50bf18ee0eaa67025521a98355f26b", + "blk.9.attn_q.weight": "3865185f4361a645b086ad47b72904c095313fb1c624e511647bf1a7dfc1c476", + "blk.9.attn_v.weight": "92125bbfed63544ab56052bd1e4aa453bbf34c795249ee54cde54907c8c6d1d3", + "blk.10.attn_norm.weight": "5d6bfbe545bcc2fcb2fc75c68f64b1f4c918badaf53e0156fe2d88aa977b2f94", + "blk.10.ffn_down.weight": "1dd9da8b0d2696ab5531fbca8a29c7d67567620a9d3e5fc2a19ec5d7e4c6cc8a", + "blk.10.ffn_gate.weight": "6e55e7f014edaebda0ac6819a426221d3b025c27312a2e18cc5806f31e3db226", + "blk.10.ffn_up.weight": "d80dde54af5db51241345ee8d64c1972608644f4deeac1e8195dc423bf27474a", + "blk.10.ffn_norm.weight": "f6ca65951d58ae3379eee8247bec34ebd0db05674cc9295593573841b8a55df3", + "blk.10.attn_k.weight": "b58e350bd6b49aba0fba4e4dd6865de3a2a0651ab865dbf2419b627b53ffc187", + "blk.10.attn_output.weight": "6b26a986e12fe66ec286a21d7d5af5eaa1bfe6f2bf502165d270e4497235a54a", + "blk.10.attn_q.weight": "3440e0e5b7e0d1e426424ae5a33f4e057be623249e9035ea12e57dbe5d3893c4", + "blk.10.attn_v.weight": "ebfadcfe14bcd6dee933053df0a67e12e7a196d5cc45728c1ffb2a2daedd5ca2", + "blk.11.attn_norm.weight": "3ed057b9576cd2de84507ef64c7646dc478c651efca4c2024cbe91a4f3fbf0bc", + "blk.11.ffn_down.weight": "8ff1c2487d22f5c499761e4eb721418f141f960160d0bab779595a34e4d68898", + "blk.11.ffn_gate.weight": "9c74e4507c7e45bf39b7cc7402198cd1dd77e3fff8c625b0413acaeb16efeb9f", + "blk.11.ffn_up.weight": "4367158007161d29939e00a322bb6776016e43f648a94f9b08a96a477aae75be", + "blk.11.ffn_norm.weight": "1cc0288c1491072121f4c9a0af20be0e13af49895696a3320e4fcac608768de3", + "blk.11.attn_k.weight": "066f5b3c144fce1366835e1ebf376f768b333b8ae29f5b478c42d1d0c809c855", + "blk.11.attn_output.weight": "e0d9f3d3f2c54aed59c02713ea4fb562799ddbacbe67ca3998dfc887bc44e47b", + "blk.11.attn_q.weight": "28d3ecc8a88cb3815e89a7f7a7d043da7a71f702b337a126e4d3a2ac1cd6370f", + "blk.11.attn_v.weight": "7c5cdef10ee73bca0a3b9f6ece5f0a0155664e0ce3d8de90ccdccfab5545e5e7", + "blk.12.attn_norm.weight": "973b133301a1af760cd7b3a7955371ea0a750808b442deb6adaf7b98482bd0c6", + "blk.12.ffn_down.weight": "d6c87b4b4ca03f75546ddd6a9e7fca720585a309188723c1ace8122438d4b200", + "blk.12.ffn_gate.weight": "2189a6e0cab1540bd05d6089b922aa8fd694be51255654933c165f302a0c955f", + "blk.12.ffn_up.weight": "5affbec19b58d092b9305721e3552481fe2eff51269ea3ed91cda3b9ef84d4df", + "blk.12.ffn_norm.weight": "f650fd42a34e950f758b4a130e7b8b1a712b1dcbede0291bb8edde47aaed0ef6", + "blk.12.attn_k.weight": "59b1e86f10450a7cc188beefc0856d2dcf44e8d7fdd9cd8859c30ec1ebaf24b6", + "blk.12.attn_output.weight": "446b0d36b2f66bd72a2323f4f4e9d85a0f621e9a58872e89a27248d6b1123238", + "blk.12.attn_q.weight": "3ed6bfd39f040301ed99fad882d3e569769d594259f9948445bef0e44ec881fb", + "blk.12.attn_v.weight": "e73652cd5d0029b1931be3ba9d82508f6696dce5a29d085476a54fb7a2ddbabc", + "blk.13.attn_norm.weight": "491b85278c0bd67bd31b9b8a9720902c244bd067e53a4a03641b7c0994782e82", + "blk.13.ffn_down.weight": "ad71cc248a85e9ced49307a24a9bfae01d387e979a7689c82ff59998e09741f3", + "blk.13.ffn_gate.weight": "0a55984d53971fab97575ee0ef5882013be7fdecfa76e3fbebb5dc85a07a14d4", + "blk.13.ffn_up.weight": "378b697b35e2e53c0de98e8e29b73d42ae3ec112ec16129aa5997a9e2f3b5943", + "blk.13.ffn_norm.weight": "f8aff2f69ab286210fad45a62b03f8d10b38f96a420d7baadf6b95d7b0b0bcd2", + "blk.13.attn_k.weight": "25ceb841afb1034831bea7f4d6a6c578def2ce4d4c412c780ef147dc9a598360", + "blk.13.attn_output.weight": "a242b322889c6bdaa14b67a7bab593db39df8eea3721638ef639abbb74d482e3", + "blk.13.attn_q.weight": "d80be9945a369439e835c55cfb0e97828b8a66bb7ced534d9059c92487bf20a9", + "blk.13.attn_v.weight": "ac33274cf9b67979d9ecdc967a55175afe0c9c4aeeff6391433cd9840c818706", + "blk.14.attn_norm.weight": "12a1e1091de5b2da12c9e7c0b1c8e6f09ce2a749733cf7d5240445b8e21cd093", + "blk.14.ffn_down.weight": "cfd41965c88266e32bc2dcdadda512499c35519e8686fefb9a7f249ab2291eb5", + "blk.14.ffn_gate.weight": "8dcfe774f07a095c7c6cf0a901c9df70d938bad7b5ba347fbc8f694e7603c0d1", + "blk.14.ffn_up.weight": "c7995577fe4a72ea0fb17c4a7b6b87b959072bbfdd5edacc6c367d43465809ae", + "blk.14.ffn_norm.weight": "81c41ebde41739e7016ffec31d2256217b825dc3cae049a935f5f61a60d22003", + "blk.14.attn_k.weight": "fb708bdebe4384f5c4b479c110028554f4d122f166b8091eda7d8d65e6780eb8", + "blk.14.attn_output.weight": "f5295caf2dfdc60553dcabe17537a80577e8b153c902247daac058df23542514", + "blk.14.attn_q.weight": "c12b7a3601c68c63ab5dc9d2599ebf3f3a10abc2c59d3a2126fffd5818f2763b", + "blk.14.attn_v.weight": "1ce968d9149bf0d5e237d52cc6d6433565b4bbf03252a736262bb00a2b34a687", + "blk.15.attn_norm.weight": "266fd2c36d7dcefc6b6bb7f1c9374c41f2bab5d6c84a063b6f91c4f682dad3c4", + "blk.15.ffn_down.weight": "6154886e9ef0a6cc08ab0d264a35f497e6f0987efdac992ed04e87088bea7801", + "blk.15.ffn_gate.weight": "183d9fd3c1b5657840099053d2fd3f72ad953b1de523296159b7761f20491a76", + "blk.15.ffn_up.weight": "51546d4498842ae2340ee226a0888d5f61e7d2ca4d052dfa06a77b0451242d3d", + "blk.15.ffn_norm.weight": "ef7378091a41a25a5f58bf1bf9d3bc64ea562e7f421e1c232b1f177c30fd3500", + "blk.15.attn_k.weight": "8d556ab8d9639324141774999b6eed0e91d7ee645bf3e7a3dcd200b2e7a00751", + "blk.15.attn_output.weight": "54aa6ba87def7cbe18b0c6ab3aff5c351cb3b6ca4a0d7b2cd5f75a1312991429", + "blk.15.attn_q.weight": "10731b0dc031ea8e0ef37bd7f010e0a78518a10a6df05a8bae48e3148b73ef3e", + "blk.15.attn_v.weight": "cbbe50c2ed7224866d3cf9b489c599f3ec41a4ea1aa3181e9f4e87e1fa0cefec", + "blk.16.attn_norm.weight": "387058eb39d4b28c04cf1368247417f1faeae8ae79d894c9f293457e0eaa00b0", + "blk.16.ffn_down.weight": "2cb26ccee585e933401ad5c82ed36ddacb3289efa0b28f8cf91b020ffbd9c333", + "blk.16.ffn_gate.weight": "d745985efb5bab42304e5d509024631efe35f92f2b2ec4931ead6db97ca9727e", + "blk.16.ffn_up.weight": "7a67bd195e0642828ca36eb7818149bb70c2c25f82de07e2b5807c520daf540e", + "blk.16.ffn_norm.weight": "7cefd061c8182482a89272f8a4e88a954b12609a62716923ca1cb3593b1c1651", + "blk.16.attn_k.weight": "d7968a2de67e755b4533e061aaad1cb62f8882af92dcad67f99d6d5112513439", + "blk.16.attn_output.weight": "9e9ab5788272ca3394ea89eadbce8c86ecc3fd75b7899184d6191c134ad9aae0", + "blk.16.attn_q.weight": "ef81c261b536c1a3a093b33f44cf2d42b86e5aa2d821674f07a0c80e992ed925", + "blk.16.attn_v.weight": "aef38e7958301b4a437cbdd2fbae6197f677b09269ec1eaf63188cd5da428d25", + "blk.17.attn_norm.weight": "28f6b289f1bc3131041e9f791b7a2a3a48baee0dfea27bf7051ebbb7ed364d80", + "blk.17.ffn_down.weight": "1a502829aafc6a9bd6bc81f12573bf8632d5c8c659f0dfb13c8b2411f3b1ec05", + "blk.17.ffn_gate.weight": "ddfd8aa0eb98846ebc9afe31366249159f46ae9815199dd70161527ed241ac4d", + "blk.17.ffn_up.weight": "4211a3cc247071bd361b30de2131d02382f552855062bf3b3e004c17992e5d09", + "blk.17.ffn_norm.weight": "647e5fa99a5b0d232af36d15816539f4d27e60a50a341b00aa88bb6e4474f8b9", + "blk.17.attn_k.weight": "d9125ff33a19c502c0f8846433ffc24395048582fc2f463d34a0301a82156f02", + "blk.17.attn_output.weight": "3d64fbb1cfef04444827f37c35fd9ad3413eb2165094d339ef89f00503f09de4", + "blk.17.attn_q.weight": "e5b29424028f578beca385fd82e29f37adedf3037cd51e5889d5a1ffb0428ca7", + "blk.17.attn_v.weight": "1809c5aaf2ac04c5d65539097564ad62796e87d24bb8b9ce5b095561a61d908a", + "blk.18.attn_norm.weight": "99daca58d001c627523d3adfbca1d95f04e590382a326866544d57989d5f4835", + "blk.18.ffn_down.weight": "84f30231ce6ca0f10227541dfc602d6418c1a210386b0c4926ef1656e7d4635c", + "blk.18.ffn_gate.weight": "ca5bbe4468b541740e54f69b9e08fcc8e478c344b70551dab21b1206acfbaadb", + "blk.18.ffn_up.weight": "0b3067b9dded31686dcfdc1e247eae3974a28a61ac59e9862758dbfaad64e8f7", + "blk.18.ffn_norm.weight": "8154a102232dbc0f90ce77ae5c1ff8f26f8b6e4dcf326e9ec1645749669e7960", + "blk.18.attn_k.weight": "25abb26021ccc481471a30e0d4cbeb7e1db29828417ec5136edeb93fecf09ac4", + "blk.18.attn_output.weight": "d87d481d9b046b68efa06ccdd4ed8cbf61e692d61114b75b7fad5ed75f5d87b2", + "blk.18.attn_q.weight": "cc6400379e15766992ff1293be79dc67682c28e9e15155a78109f4b64653b164", + "blk.18.attn_v.weight": "45c75cb1dd496aea3173aafe2575b841dd1d02cbe010b3198099731eb98f531c", + "blk.19.attn_norm.weight": "65389efc75297684773284ef8e5f8789a4504b636c9f33b8a32e0ee42499fa72", + "blk.19.ffn_down.weight": "4eefab7e939f64a17e4a214ca3c77a6fa110d94f677e2d6401086f70fc538b04", + "blk.19.ffn_gate.weight": "f1c0a59cafda66f466ab585b0b8b4861b58abe87a67cea1f6a488492242edfdf", + "blk.19.ffn_up.weight": "c42d045eef588db4a0e56960a57e110e1ff92eb8041107d19899165fd3b90f17", + "blk.19.ffn_norm.weight": "a8f33eda6d5d62ff5f333ad9771783caff556641f4e7df713451385676f441fa", + "blk.19.attn_k.weight": "0bab5d9e9083492bfb05a5a3bb23b79c0e7b99ef6a6644817b4d57d5c453b8a5", + "blk.19.attn_output.weight": "c99c551d70eafad0f7aea98fb6f9251635897168eb3895f76abf0d4ea3b3aa6f", + "blk.19.attn_q.weight": "c98bde95627c3b54c9443813ca50b4e14f518319681db6bbf7b2332ba26e9a60", + "blk.19.attn_v.weight": "ff3a490518cf64904db89ce0dc7d6eb89e870f1440e41883c6b55a221f82de84", + "blk.20.ffn_gate.weight": "761f0e317229cafe9d3754048ab038a0a84e9a287b196ab65f633139f2d29aba", + "blk.20.attn_k.weight": "45d13439b41066d282e8490a726785abf513605f46c79bd0c840f6419d27e790", + "blk.20.attn_output.weight": "a3b958d84b4a097844179b7d55c18fd0e4f319cb15e918c6fde33b68de1bcac6", + "blk.20.attn_q.weight": "127ab8e7d8c3f882874904196a02712bab42e6744fde45871b67350609d19f5e", + "blk.20.attn_v.weight": "5f0ad2d14a8ae42dd3bbeccfb33295687a14055fa92c54bc946249373c1c9f17", + "blk.20.attn_norm.weight": "77300b1755edc8c70089e0f45efa646056b9add7d8568b2324d2f3e62b64971a", + "blk.20.ffn_down.weight": "ab93d0e075b42e9017b701a070d561e698050d90aac4b4b9919256fbe50c3204", + "blk.20.ffn_up.weight": "4fd6628a07acc57a48d1ef83f81b7d7aa0bce569c1160a99d307284f8821322c", + "blk.20.ffn_norm.weight": "2a9e46b9e48e8e55215de56592e1f189530037c1c94a1428e3d6f106c7f26fb2", + "blk.21.attn_norm.weight": "4b3b5912c7bc61eb9da8e47d4651f896e85d9e59c4ecaa65df7acf3c21737298", + "blk.21.ffn_down.weight": "7146f931663d93b8771cd84405cd4802ea6560d0729b0d6d44588203c095bc53", + "blk.21.ffn_gate.weight": "b44ec5d64388fa40b90b3e9976d97a8b6800fa3b97584f32e64b03daffb8601f", + "blk.21.ffn_up.weight": "0cf3643fd23c685e17062cd11e116e17ce57a405e5e78953bab94cd62fe48789", + "blk.21.ffn_norm.weight": "4ef2cdb53da166df70b39f3e6b17af51848cfa5ea3c27ad6a1ae2a1bb1da1ce9", + "blk.21.attn_k.weight": "5d40f32a706f670c19972b14176bf660d5b045e3637b110dbf8d7de4ff32101a", + "blk.21.attn_output.weight": "18afaa916752ce16c9653ec0ec7e2fe60be55faa2aa5025d147be184adb75cac", + "blk.21.attn_q.weight": "2621daa5f858931514a4b2f0fe8d81cf9b96f541e6af99bfa7539e9bde8e34ee", + "blk.21.attn_v.weight": "63226dafc54c899bbce4aa49efceeedd8908e94faa613450fdda91f332b62864", + "blk.22.attn_norm.weight": "cf3058daab4d2c04387e7d169d1553bb8e7358eea66285ec067703f6ce62043a", + "blk.22.ffn_down.weight": "6a58d5fd220abdbac6cee7ba048abab794731af318f04982c2506df59413d0b3", + "blk.22.ffn_gate.weight": "d5614535324b03c7b91727a903b2a72f8d07ad17f7aa8b61ea173cf9b895069e", + "blk.22.ffn_up.weight": "ec20da3949566e93f66cabb67f8cd7eab399047ec6ebf5d43edfaf3669b82296", + "blk.22.ffn_norm.weight": "84c82f38f53a649972a44466fc476bf764e064ce18de870291edc302f3700e28", + "blk.22.attn_k.weight": "a3d2ecc37fde7c201176bb8abadf27f0d8ede9679a6034913e03d9db924fda12", + "blk.22.attn_output.weight": "5a3b8bb433f43a387df43dd371bdf80ddfac986dfeaf38e9bac1d7a0ec6628de", + "blk.22.attn_q.weight": "3a875cec661b4859f30a8fd2c866811184b25b68c9e36fe2663d299caf8b59c6", + "blk.22.attn_v.weight": "8717a83b79035058dcfd3ef6f8e5b36e71d77379e5a239e1899eef8766fb7703", + "blk.23.attn_norm.weight": "2b4a68a0a2f023dd646e4755c9bef17c2f631901154afd839edac7ac006ec99c", + "blk.23.ffn_down.weight": "29499b1586c6fc4883c9b7a9c8cf388035146b5aecf90c5c4c8c8e082c71e7d7", + "blk.23.ffn_gate.weight": "7d6554036d21c587b9b556428054f9c15cbef96d24b257f906fcef4ae38bd9c8", + "blk.23.ffn_up.weight": "19761ecb288d6ebd44b681c4535661583b1e19dc29e96d0c007333cd8f00aacf", + "blk.23.ffn_norm.weight": "37dc35500790a4ca33807b39cf7af65065e535dc25b9e94f3ed2759f61887ac9", + "blk.23.attn_k.weight": "717547d00323817b0cb40a72ec5f8cf42ecd1f9e3e42715c2cc5e38f07fffffe", + "blk.23.attn_output.weight": "a24786feb6a905fdf166d7500133757cbe494779d4ebcba9eb03046b319557df", + "blk.23.attn_q.weight": "6a2c4a98f138b928d22136efa163562691d3b4ed526d52d46a2fa2694a8f3965", + "blk.23.attn_v.weight": "c6e6081eb9c38a7fda023085957b460e9ea321e1fff408b38c2b58595c39979c", + "blk.24.attn_norm.weight": "5e6283f891e538670425f3e244b08dc6f96f33dfa4aefa913f8eb17212421850", + "blk.24.ffn_down.weight": "e09eb170f389deea0a4a1cbfdb52c12490768a2c60491b7bef8a4c445e2a08f5", + "blk.24.ffn_gate.weight": "af29d815cf49a38fc2ebd0bf9b2dd9933d023a29f2d766981acb9a1b53f09117", + "blk.24.ffn_up.weight": "36ccd9333426666de9d3088bd4dcdf5b624b09dca9e3a83a22fc0383f2d950fa", + "blk.24.ffn_norm.weight": "a88e1692318826db6ac42582d182e51a3c698c655d0e21e04fa086318832d07b", + "blk.24.attn_k.weight": "f7d61d6d1225289bcc502e3bbb0168b4584add0253218c1b77ac92ccef9a1c2e", + "blk.24.attn_output.weight": "85a1363b3ccc87312094c2195022687c16b0dad7fafb9e80bb4ec474d53c29ac", + "blk.24.attn_q.weight": "53482a2c008f42f4fad779ca323addc3712040149dfc12f782417756388a72bb", + "blk.24.attn_v.weight": "67498272369af7dd10097c73b07f731b565cfc9a559e711cc0d526389e7b44e2", + "blk.25.attn_norm.weight": "98dd617def5cb7825ee4833132ca2da2121245921585e1d9e36b93344adc321b", + "blk.25.ffn_down.weight": "7fd477d6c50aed5f424a878dd284343379cffbee8a34c0b6e55100c8305fa13f", + "blk.25.ffn_gate.weight": "f892c9806c8ec22e8aa746734ac9213428c534921cf161239e1d249fdb5d1ec0", + "blk.25.ffn_up.weight": "528bed14c9bf9762f790525ee40412545221f4321d2a2323fa8e73c58b7643c5", + "blk.25.ffn_norm.weight": "ca5831966672e7be6a578feeb631ec3570d3b5afe12860819ccb96e896ffc346", + "blk.25.attn_k.weight": "610d3068cc9b20401f0c3a0efea39a279dd9f564fde19baf3403b2ec2319e4c4", + "blk.25.attn_output.weight": "798aaf702e53b657265ac3b5e6caf3a0ab515bdadfeb1a3a156b4f3bfba76666", + "blk.25.attn_q.weight": "8a7fa25248de83029fb97b51d036a01baebe31fcb4be121ab00dd8b7de209b10", + "blk.25.attn_v.weight": "2a53d5e9f8a1218c66958c6388d3b37400a9af7956c785024ca44bfbc3c7d371", + "blk.26.attn_norm.weight": "5f44fc043481eb0771f3e6d2420bcbcf73140afb9a9feb8eddb6575452acebee", + "blk.26.ffn_down.weight": "944a60a409d0d5b6a851e33c69aca152454b691711a8b96f5bcc488772ab2833", + "blk.26.ffn_gate.weight": "2a0ca4abb3de5593e6693d8be69b63d6d1a639855ac8332a75f520353f030c62", + "blk.26.ffn_up.weight": "0b1df496163f9ac07bf89375d3eb441b51a81d41b47d769a04a61efc18dbe35b", + "blk.26.ffn_norm.weight": "56b8dd046e9be6ea71f7efd80dbd14e7fb1aa020d3cd38e063275f3873fd12f8", + "blk.26.attn_k.weight": "b1dabfabb970e6971c7ea6e53c63cf7ef56341e6a2edd9cf177785cad9af2f9a", + "blk.26.attn_output.weight": "39532c7e836baad164a655fb97ec5114ea4da37ffba9fdea2684f6e4450e6f84", + "blk.26.attn_q.weight": "8f48bf6aaa1252bc149e98af2be1777a5c0d2c3274c6d314171ea9344a41b604", + "blk.26.attn_v.weight": "02fb145f7fd905133750e90571effacadddfd3f4966552dc59982ac3900ab8c4", + "blk.27.attn_norm.weight": "654d168fc3cab716d91261f5719f180b7d697218401633b4878a759f1b5283f2", + "blk.27.ffn_down.weight": "2823272bec3a1c12f02cc4cb24aa4031abd7e9dbe0b02676e2305b21671818f0", + "blk.27.ffn_gate.weight": "b1a1d40cd02f97182cac17a79971d1934ee0daf3aa0bf11303568c636e208a64", + "blk.27.ffn_up.weight": "ed62ec72a020d070e64eb7b50237b32213944727b5b2427f45d989f50df5fb2a", + "blk.27.ffn_norm.weight": "c69649ac65d694b306a905dee8b03b89eec1ed188b1eaaf38f8e29d4b12e38a0", + "blk.27.attn_k.weight": "cc57bbf413f1fd227128dc66efc8590c73634cbd6f96d01ec4878b5e7ca6a925", + "blk.27.attn_output.weight": "cac407ad02361d53207b3c7e25ceab84dcb4347b8087055162e2efe14d11d84a", + "blk.27.attn_q.weight": "0af18e07cee12015761c07c94407024f4f4d77d97bdb24163db0e16669e2cef3", + "blk.27.attn_v.weight": "a1d08fbdfa40af773c5adcf93bd68b78a44ed144e3fc6bbeb8af02e937527eb6", + "blk.28.attn_norm.weight": "f39a51f814512b040a1082143150e4a49ff730f85cef49d7f77fc79d83e91f40", + "blk.28.ffn_down.weight": "74f29ed51055d1c1adb8f0660bbe538a27e016c65650f2d67efc6f1c84fa1b45", + "blk.28.ffn_gate.weight": "ae48bb16487ded6781c60aafc0bf738fb4ae15729952906f247d216592ce249a", + "blk.28.ffn_up.weight": "543009727718ac22f11ee4b17815f68ea6f15ba1f3e7ed5ecdb755cf6417565b", + "blk.28.ffn_norm.weight": "b8f9e54c322079ff20a82b88948cdc2916c22c7db40b9a9ed6d3cbe89efb727e", + "blk.28.attn_k.weight": "55d055ba653b728d6e784f9e013786fed07115c9fdf23367e3941386d5e77db8", + "blk.28.attn_output.weight": "155101c03ddbf18f4fd0694bfc982f33c7bae25c9b087d6f5273c2bfbffcf2c9", + "blk.28.attn_q.weight": "1ed19bfdd22e9c14eca014739982492e9516d411515a8585f65cf754d849e53f", + "blk.28.attn_v.weight": "11ba854dd575c025d37256eee9041f6d1bd2b549a083d6409a09bfc1542913f3", + "blk.29.attn_norm.weight": "02b0bf5e2fcefd11a153cc988c81ba672682e4844fcf6442423e21a0e10d566d", + "blk.29.ffn_down.weight": "594bb692ec2779938721ff4748666ca8370e0e4fe85229503f616438b8884f5f", + "blk.29.ffn_gate.weight": "8bedcf47e91dcb2cf4093de56b048ee411faab6ff472f89ab2c9c113a08e6967", + "blk.29.ffn_up.weight": "e241a547b5fd6dfca8200b8141e21c1c487a96cbc4e5855f181a7ed1be91b642", + "blk.29.ffn_norm.weight": "e63eba5e4c6b288bfd9f15e46e236086456c8b7f1f9c732c0b5de84962a2e7cc", + "blk.29.attn_k.weight": "afe5979d5bcf211aebb526620f5974bcb0a2c39c8be71e815575c55d6385e3aa", + "blk.29.attn_output.weight": "9c944ed44b124b014906fc240afd3b90aed56bbd9567f2eddfd5b7a685b3cb48", + "blk.29.attn_q.weight": "e234e08e5c1bd9245a2edc8d63e9933b6b879f97c01392209cad4f55f05f3ada", + "blk.29.attn_v.weight": "5cb8e3e5f954e775c5a5e4de7a9a62b17e9c6931bb0ff0e2f82c4126fd3e1a1c", + "blk.30.attn_norm.weight": "a65483ee51a0b214144ec8a14f28ea5437586e9e12ebe342a57d1f8627ee12af", + "blk.30.ffn_down.weight": "417959da77ceb33ead4271cbb9428b195196173a893c44e52880a7ec61b4856b", + "blk.30.ffn_gate.weight": "a0d503ffcbe45dc927600bb98c9f6082487e65cb577ab545add400d666a87638", + "blk.30.ffn_up.weight": "f8ab957b82ffcd10b21303cb5e866209b6fe95f827b1b94e9a949207952d12c0", + "blk.30.ffn_norm.weight": "210c7ceb0514a9ef27b5d4d1b3aff6dde43f1af0345a050d71097940e0e73e03", + "blk.30.attn_k.weight": "16861b9abcf5a3fe73c93d977ca45a1e6daa65be0fd85c2cff53486ce2033afa", + "blk.30.attn_output.weight": "ca541fb2e57e2257118c35784845b0c731278af8db3036ac53d71aa1681fdbdc", + "blk.30.attn_q.weight": "f7834917748e26bb456b945e230bc926c228e93696bc01fbc2b134bdeeac71a1", + "blk.30.attn_v.weight": "9292783171dbe5eb689d17c9bda11e537f0e9b328fced6986c938d61ed590e81", + "blk.31.ffn_gate.weight": "e4766a04bcd8f937ba883c6a144101e546747804ca66c35c97281d6ccb47b566", + "blk.31.ffn_up.weight": "cc1e666116f7e6b06736db4aa4b81003c583f54f4d9200bfa48842249940e16a", + "blk.31.attn_k.weight": "fc80b57557687504efae7d24265cb7dc39b8f826bb3d897a11783012dbedc44f", + "blk.31.attn_output.weight": "215617f50a1f5d9b2250b82f3652b35a9e9aa0ad9ef2b485d73965a14b2b872a", + "blk.31.attn_q.weight": "274b4f1dfb0bdec28632705677049fb3e327ce6d9e1f3baaad1560439039982f", + "blk.31.attn_v.weight": "e641b8b926f9dfcbbf6b6da1c02555525ac4b1c306d96f20cfbba7d6662c4e56", + "blk.31.attn_norm.weight": "b3243c361d4041ddb892ce6862dd5091f57d87357e3c67e177451b85d8baf34d", + "blk.31.ffn_down.weight": "0a00cd3ecd5e91624a27f9e239b1de425d5ba3cfff82c256a11a4ad434abf3c2", + "blk.31.ffn_norm.weight": "2a0d67ea2bb1303975712243f07273c92fce83baa11b1cd6d8e42e74ea3c810b", + "output.weight": "768615f077fb797967844571c58b94d7c399d884d115be3ab4b0154504cae892", + "output_norm.weight": "7cc5b7ce10e5082000fa00bfa68af8c7c5da218e59e2c41cf2f1499d40ca229e" +} diff --git a/convert/testdata/Mistral-7B-Instruct-v0.2.json b/convert/testdata/Mistral-7B-Instruct-v0.2.json new file mode 100644 index 000000000..1da4d2ad2 --- /dev/null +++ b/convert/testdata/Mistral-7B-Instruct-v0.2.json @@ -0,0 +1,313 @@ +{ + "general.architecture": "llama", + "general.file_type": "1", + "general.quantization_version": "2", + "llama.block_count": "32", + "llama.context_length": "32768", + "llama.embedding_length": "", + "llama.feed_forward_length": "14336", + "llama.attention.head_count": "32", + "llama.attention.head_count_kv": "8", + "llama.attention.layer_norm_rms_epsilon": "1e-05", + "llama.rope.dimension_count": "128", + "tokenizer.ggml.model": "llama", + "tokenizer.ggml.add_bos_token": "true", + "tokenizer.ggml.add_eos_token": "false", + "tokenizer.ggml.bos_token_id": "1", + "tokenizer.ggml.eos_token_id": "2", + "tokenizer.ggml.unknown_token_id": "0", + "tokenizer.ggml.scores": "e3d3eea80bb41a1213f2d0aa3e8a38581d1f19323be77dbd779c9c7e3b72e676", + "tokenizer.ggml.token_type": "6040635e6bd38d98af06698feb75c1802bad35180ee6ae0a503e38c0f60fd71e", + "tokenizer.ggml.tokens": "604ac4bfbd019e430d7b6cdf18c6c0cd5b967900601f0307f714ec7773aa5ca6", + "token_embd.weight": "cde834ccac5e94324b25cb81b02d27312cac0c551b55a7e1d555d90bf6cb6e81", + "blk.0.attn_k.weight": "458bfdd9715c66e017c2447b1ed3c582963a3111479314e664faad8c914f42be", + "blk.0.attn_norm.weight": "e1fd60b95f713bae7b7e3ca933c64ae6c9cd1e8d808000204bbfdc19f0ba635b", + "blk.0.attn_output.weight": "df13b6a157d9d4f96c53b012b3b9bcd207d0c94144cbd22ae3ec13bb07d6c373", + "blk.0.attn_q.weight": "13b4126b4245bf06c915a93317c42b8174e05053535ec99dc576541e4cec7c25", + "blk.0.attn_v.weight": "5b1781d3a341214511b27eb4e268674ea3ea829dbdf8ae5a6bb89b3c0b33fafd", + "blk.0.ffn_down.weight": "49186f5d8148d316b07458841d13a2e66587f4af69b776188a809591ed9c070d", + "blk.0.ffn_gate.weight": "4397e30ece09136f00f4ff84ff49e5241b765a374deb8c5a12e897e2bf73473e", + "blk.0.ffn_norm.weight": "43260589aac3850a779bca3f9649f793bbfbe5db538361cb743b3830217f8287", + "blk.0.ffn_up.weight": "fd7ac918240a07566f6967527ffca58fcf433a30b78fdd6d84b2136d4ebd9987", + "blk.1.attn_k.weight": "209839566c7d235bdc20565a4766378b6ee8553133a5a3315abe8a85baa80712", + "blk.1.attn_norm.weight": "58c52986f7c69784ba327cb7f350923420782bee17fa39b1fbd13839d4005357", + "blk.1.attn_output.weight": "5067cc628449682665dfcf59b16e58fe2a9d2a81cb099f0fcd42f4f8670c6740", + "blk.1.attn_q.weight": "f410f9f0dd5edc09401af597d02e2a4c727f1502ec3ec3898321617b36c6df6b", + "blk.1.attn_v.weight": "d40fa49e07c102c0644e130e7909eaa93ed0d54e2edddc0759e721d58a4e4f5e", + "blk.1.ffn_down.weight": "594b1eff6ed4defbdd819fabbe2d48764984f08878a860bdb808511d5a25b8db", + "blk.1.ffn_gate.weight": "4cda97541e388a5bb607ce4cc8b3db1da7045830a630e7ba4d17807befcff346", + "blk.1.ffn_norm.weight": "66c13d7481be65b97aa474735ddc9674f33d512ddda76fa6fb45c7464b09f1ed", + "blk.1.ffn_up.weight": "1adc6de288ba4cc1237833ca8b4eb81107149842e38bc452e18e5cfe284338a2", + "blk.2.attn_k.weight": "5420423559f236ab22d85a00849f31e0cc6e9c7dd879de724393d8cd2b379153", + "blk.2.attn_norm.weight": "495fe1ab40cc52aa054ddd4f0c2d2790f4326c8d103296b1b38f3b1060db2a24", + "blk.2.attn_output.weight": "ccb83e7085381f558bfd65588c525ad2671feddcbc3887afb4038ad9c7aac348", + "blk.2.attn_q.weight": "2e8f77478392bc93c2a391f2e0f4a173a952bbab88a7aca099c6ee909726409a", + "blk.2.attn_v.weight": "d64512590f3b7ebbb9e77c2eb97fbda90b00d45c944f2b174f03a2cb11007567", + "blk.2.ffn_down.weight": "1de5084a05dcaa6b1bd926e83517dbe9ebe7fde79235fe56018b3028b1aa6397", + "blk.2.ffn_gate.weight": "cbea526b557f49aad8c976973cf367fcd12175b900f551984f498b9e07e4b7fd", + "blk.2.ffn_norm.weight": "530aa49b10c7eae08899d143409240deb95dae4e1d5bf78cea3b26393cff3ba1", + "blk.2.ffn_up.weight": "13a5fc19b96b4dcc1e9bd01998c8272ebe52034c1933ed123a506b711fae9a5c", + "blk.3.attn_k.weight": "1913b63a73305941d8cdc472e7f101c633d3357a78602eac0a4b49a744261075", + "blk.3.attn_norm.weight": "9c11bed5ab41f4adbfdae4ead65b525c8f19443e656a8c61ba412a4e1ad1193b", + "blk.3.attn_output.weight": "bb0b42c1d34779c5943272ed71f1dbb31ad8edd75f8bcd5c868f88505ac3a610", + "blk.3.attn_q.weight": "3461a1fe4e49f5319ea047cae98ccdb46528a3ec23831183fe87610b48c94948", + "blk.3.attn_v.weight": "82aa30be6a61526a41fb79bb28a2617416f5909f0477aa9e95e16be9370fcb38", + "blk.3.ffn_down.weight": "68521011ae03f5e3b0966127111afa8ee9f2eaeeef8d3a0b86b633e0332e9fbf", + "blk.3.ffn_gate.weight": "1e89e26338fd364bb679695968c65106382f15ad55c95cbb5ec9bdfeb766f432", + "blk.3.ffn_norm.weight": "c81932529a5a8c417c27b888dbe95fff8b447c2ea5f6f560444ec5d50b93832c", + "blk.3.ffn_up.weight": "305021735afd8669afefd713f56137248d5e817e60471a112ad06b7fa07ffe88", + "blk.4.attn_k.weight": "cc26ba5c5c28082a79e6abfe61186029e80b145252ca6a7924c437f0bcf2d51b", + "blk.4.attn_norm.weight": "302d251fdcc91f7468cf33f80b49484251d8917d7018ad264ab3a85c8ecf9ddd", + "blk.4.attn_output.weight": "a012f5bee3520cd4ce51f0076c132ebc3653309f304032ad051aa308f55f36de", + "blk.4.attn_q.weight": "3c8d607e447f5ef21e73af71e3c0d32fae16f91f31faae34ff06912cf9cb68fa", + "blk.4.attn_v.weight": "49f6c81a634ce46d71c2350206ecbd231b1732af96e4e4e67693c41a07e007d8", + "blk.4.ffn_down.weight": "e89504f311a4a34dc819a67b761022f14d71c43df3ead4f892c87aaa8e9f0adf", + "blk.4.ffn_gate.weight": "18b22f079a2fbaefe3572eec61fdcd996fd747724e2f0ff4f08cfcb43eb7bfb6", + "blk.4.ffn_norm.weight": "22415a492c168a0878912b05c854a631228b01c3ea8842e1d75989ec46c18a65", + "blk.4.ffn_up.weight": "f57379eae2874d8853f14ddf0f0fcc4ff1338574d5ed5d7e88331d5fb84f5642", + "blk.5.attn_k.weight": "d627af853c40bddf9762ce3988008c1ff17f2686fa8f73a0b5da38010147c316", + "blk.5.attn_norm.weight": "9ce01092c7f7f1c3ef72d6b794da12d77aa1f6a24fb96ba1b9bd5a0bcc3e2443", + "blk.5.attn_output.weight": "0388da8064c4b6b795ce2d8079e8a36535e82b2c9cf794e38ce8ae460aae726d", + "blk.5.attn_q.weight": "039b7ce1c909761fdf475c06cf14cabe5a90199282c89e4dcf460e95a4b6275d", + "blk.5.attn_v.weight": "c47bfd8d2496bdb6e00e03b903e15fd0ee806a515094ec257e43cc433147ab7e", + "blk.5.ffn_down.weight": "1d62e6708974bae318cbf00a8bf621d9ba0537e549ce4710a536520a8d14168e", + "blk.5.ffn_gate.weight": "8b42b1b11c92db19985094cbb50434e3a7c9cfea71ee6f21ea79eae7c49284a5", + "blk.5.ffn_norm.weight": "e0bc520f1505e687ec391d632a381d38d8ebcdec19f614a11a2000ab573e8b7b", + "blk.5.ffn_up.weight": "8cdcd17d2ea89bb9ab902dbc6bf3f827fa4ee029c6bf19eecbdefd146d8b6f2f", + "blk.6.attn_k.weight": "5dc6bcff89794d1756bf57ec665b58622d9352130d31082a6c66e1a079f99932", + "blk.6.attn_norm.weight": "13b26008abe0f119b5104b9d78ebd5e797d3cdd68122b93d73a3b4831a54d085", + "blk.6.attn_output.weight": "f5a49917ea70c3fb311ccfffbfafa63ab18416a5d55e5429b70ce8bfba57c075", + "blk.6.attn_q.weight": "d9c2f652c87dbd09ec3822e12876648fa32e86553ac25afab723b1cd9f8cef90", + "blk.6.attn_v.weight": "5ecc5fe67609a35151011cb526f45c56fc0a999079ae0ff37c755ca03c68c555", + "blk.6.ffn_down.weight": "0ec125ae0ecb2d9277fdb1b04f17efee94e37d0ae37311057c212ca2db3fe6d1", + "blk.6.ffn_gate.weight": "fa4d6d38355ee8aa3b80b476d65ae7e343c9b7770d7b097fc848ee8a6e091d1f", + "blk.6.ffn_norm.weight": "30e8f7defc627532e1739dc76d31223d45767391a431f925b63dabe334b0f392", + "blk.6.ffn_up.weight": "6b97cc32b290fa9087806b5d65aa6dc1760737730c8c71394cc4f30c2157f9ab", + "blk.7.attn_k.weight": "0231cb127cb7c3714cd72b8f39343891d7715a9bab2237ade9e7bc5f4ed2e68a", + "blk.7.attn_norm.weight": "7c3187f07eead7d219d98ab2daf87905e88d5f1ace109b6f5fa55dce3914981f", + "blk.7.attn_output.weight": "2f30ad972c284ae7c8eb0482053433495ebe8fe9c5ee2c28b4bc4ed1f33050fe", + "blk.7.attn_q.weight": "3a2b4b8d61cc9956d304fa9f82a9e65b4bb9fda2196670b16df7e0d8c43eff2c", + "blk.7.attn_v.weight": "d2aab97d0dcf0f61dd2f32848f7a8a99c423a4948a660a660a03a546972b8db8", + "blk.7.ffn_down.weight": "2270d520468c5549cd30023ff9c452a277058310104c4239a616373fc5a94387", + "blk.7.ffn_gate.weight": "4134a3ef71b3eac8f76b6f1a2e58625b3bae48081f175994bc3ed7d8b0d4f2d0", + "blk.7.ffn_norm.weight": "42df4abd4b8769b16f3930068f96960af1b061f1aeb7505384f272233b2badff", + "blk.7.ffn_up.weight": "c920549054ec16ff8c73a72f5d837cf4e11885e44db57c1c1c584c18fbd7a9a5", + "blk.8.attn_k.weight": "01c609bd3bf31ce65688f1f640ee413740e821330134d4ed1877a3065d1527d5", + "blk.8.attn_norm.weight": "48857411f769b00290f4e4f2e593e092781fdc2503f80c1e3eeda1b85a20f74d", + "blk.8.attn_output.weight": "90fb273f8df83744554bd59236515c16c5a5a698ca3fbedc17cc89ddcee354ff", + "blk.8.attn_q.weight": "ade617ac4653c7f00593dbb51837a468afef20a14eaab3780fb96ac3d6714369", + "blk.8.attn_v.weight": "c2c37496494864fee5c527d1fe1f88529d31c73f9cbd02ef9b2e9b23611ea50f", + "blk.8.ffn_down.weight": "2da58572e9ad79087c03cbb0c23c9ef69f93ec221fd5fe4ed92fb93871d23ffa", + "blk.8.ffn_gate.weight": "4483294e628edaa4901708e73e92c917bdd93b780fa01aa74aed57166f2bbf0a", + "blk.8.ffn_norm.weight": "c0cbb7a4f8123b62f0c4652a687f3b394802bc32870dc446eefb709e42043a7f", + "blk.8.ffn_up.weight": "9eaf8a2060cb9224cd585997cd671866c4051ad885c2c6d9fdc7056c2a5c0d89", + "blk.9.attn_k.weight": "5dd36c45fbc9c50fd35c36cd75576288506971eac5c5311d4f5c16ef60099645", + "blk.9.attn_norm.weight": "3c8ca64f2f75ed7c8fc1da010c23be787648139a96ca0ef3ad10be7b14942b8d", + "blk.9.attn_output.weight": "6277e1f833024f53c409be919ec76d34464a78b278c8f9dbf79e777746e3b995", + "blk.9.attn_q.weight": "87352b70d9e328c2d51d59090cf5ea5a046529864a890d0bc8986447a0a5c006", + "blk.9.attn_v.weight": "2efdf01161d7a82a9117cc2d87d37dba5ffefcf730781cb94fcc95130e48ff9e", + "blk.9.ffn_down.weight": "e7658a2ca984961c7ace16acb679387bedb1fef656b5330bbbf588db19673a75", + "blk.9.ffn_gate.weight": "773cd330d4ff5d64be8af00adf2e2722fae4e33fc26bb9d03549f6f4b3b0fe57", + "blk.9.ffn_norm.weight": "c8b86cd5c43b332f72060b807091c33a258e5dac01358ff4733b916cd34c9c97", + "blk.9.ffn_up.weight": "d8cc3bcff18bd46124ba2aa7caacc71220b44eeef6fccb993b4c6cb53e8f2c3a", + "blk.10.attn_k.weight": "964bdf3b4e77b915a216f750ff7b0f2eb1dd6bfa071358aef21010b90111044d", + "blk.10.attn_norm.weight": "59ed411d91d14775764eb514acb0895a75a10cbbfbc1c15d453bc50f8046cb7f", + "blk.10.attn_output.weight": "4d35a2a44cfe4ac0a83fd3ab0dcf1f5a0bf54cdb3b7be9fc353ed32c8a3eb81c", + "blk.10.attn_q.weight": "defff5339450dd881ac352f5c459293f39e07b9619ebd10ed632d79a3f310278", + "blk.10.attn_v.weight": "b9803e8d6a54acea58f662d4c0a5c8ebdf986676de7dfe12d4b288937881ce93", + "blk.10.ffn_down.weight": "eba856be64e4be20b92fb4639a783454dd92427250759df92a337e39f1971c08", + "blk.10.ffn_gate.weight": "2d5c509b066584db4de3632b01234e86edcde35409c5ebce18957dc80fe465e3", + "blk.10.ffn_norm.weight": "ecb9a8679945ff0273856624ce435dd250ffe5a440ea0861a5c84f0e4c44d2c6", + "blk.10.ffn_up.weight": "e76ec7e993f399af02958778c643aa78368e3067846714165eb5aba9d5f547f5", + "blk.11.attn_k.weight": "29c6d1f34bd3ba2f0904e57b32a5bf8dcb2834d439159a33edf234ce0b775677", + "blk.11.attn_norm.weight": "b5817b275149cd2abe18a6a10e19854605fc58fd364666744362ceee8cfe49f4", + "blk.11.attn_output.weight": "1e05653220e237cbe0cc770033e183c9a0eed5680510997409b16186c6691950", + "blk.11.attn_q.weight": "03db725ae669151e4d536e50285b3b047ad097f52475df208ed3e790e31a44be", + "blk.11.attn_v.weight": "27cdf1d4e971326c451a4615a0b79a8c7fe9508f9b76c0d52fa01971fc7eb403", + "blk.11.ffn_down.weight": "176938cd7c2966094f614cace8ba568b10532e45a0d438f80eccd19b6c2a7f87", + "blk.11.ffn_gate.weight": "9782339915dd6fa70013628a01524ee1d01ad8beab04068da7ac6a5ee7603a60", + "blk.11.ffn_norm.weight": "8245f6391e3be97811c0ff27f0d8f484ecc82a468a837c893f059745bfcd95eb", + "blk.11.ffn_up.weight": "15616ddde096d0d25e906375c548b6de4bd5576d1f6b68eefdc29f14e183af42", + "blk.12.attn_k.weight": "66dd21604993edd1b1fe547bcaa06f5bb7e31c9204902d147a227e4badf7feec", + "blk.12.attn_norm.weight": "23a69f85dd8a0904b9839cc5d0afcda299b74e82ae2642106224a1c820f2b761", + "blk.12.attn_output.weight": "4a98d132e376beb274a39d4ea9b6a1b870ad5c66625439d7ff6f45c229c3ca04", + "blk.12.attn_q.weight": "1c6c309d63afcfde32fe37257e300a78e25d01117e33490801107c0e75d1ea66", + "blk.12.attn_v.weight": "723d9e4ebe4e2b1974afa01d8f512b52933698fa36717dd47b37b07760c50a10", + "blk.12.ffn_down.weight": "00e0fb09e1f1fbbf3803f1dee373eaae7a93756b6e13063ab77f9927bc6f996a", + "blk.12.ffn_gate.weight": "89159f7f97aefb1e100107e3ac2d694e1008ad873f79bb953d60c2c1bb22724d", + "blk.12.ffn_norm.weight": "5f70aebd0e43a39d6373d8658cc670c13aadd7818831d3d84f761d5f688442f0", + "blk.12.ffn_up.weight": "faec21b446f061eb4dca561a3180712724347b77a71eb312e7afe9be9e89fa04", + "blk.13.attn_k.weight": "3d440825d19eac3b1753b34d94fee2b3a3cb6636c10b2703ffcf688d3c1eded3", + "blk.13.attn_norm.weight": "47b575e57e410738ad13fd3c74bb49c06b3d31030910834ece509cd1a5c6d9be", + "blk.13.attn_output.weight": "05436d8e613f4475741c1798a7c371b53d61b229507fa04fe23c504ba1f0e12a", + "blk.13.attn_q.weight": "002b5024ce520da41256e3ded5cdc60e5ae07ad9b202cb19d76ab511efd02b1b", + "blk.13.attn_v.weight": "c1f2d6763587c50312cee0d7140fa2c7ee326f5b172bc99b2d8946e08329cabd", + "blk.13.ffn_down.weight": "b5c4e0d8a3ff96cd76a135e415b89f02d28c28f7f3c16a36af31ef0ab8773da5", + "blk.13.ffn_gate.weight": "ae06e9e3d2e1f64c7ad23a4009dc904c2eccd7241f9f91c4974ab2504f116be0", + "blk.13.ffn_norm.weight": "e44a22321bcbcb4a3c345b504e939e8071370f54a8cd702fabdb40b97e0d7683", + "blk.13.ffn_up.weight": "7e6f366d538e21ad431264b12c011892d0be9dfe4c4da9f730af677f920641ba", + "blk.14.attn_k.weight": "95492d6417952ec24b2cab87bceb750fc7e95ac6b1944fc328a3852d980164be", + "blk.14.attn_norm.weight": "6b7b09e1c51addcdbb160ea59edf032531421c520ec5645fe1ff9ca4180cef54", + "blk.14.attn_output.weight": "75887474e4d72c218e6ab0f69f1bf3ec3dc414d51b36fc59df00cdb23421bb6a", + "blk.14.attn_q.weight": "940e33f76e48c21215d19e8a21234c8246d4d084381a7d9806aecb24b071d5bd", + "blk.14.attn_v.weight": "c58601cf5a9833f80f7f9a5b2656e8eab5eb133211446ebd48f8be15fed4ebb9", + "blk.14.ffn_down.weight": "f9f886e7f9b2a54d717b08947a25a0a93e8c2a5b8bcd5a907c06817c8ee3ac11", + "blk.14.ffn_gate.weight": "727ed0ee68594a3f59d704ed3240b6929f083b9c36650fb848d182315737245c", + "blk.14.ffn_norm.weight": "bd2471008ff1b2bae9aa26bea019393fb2bbc5b9493b8cec3ebd2c280fca24ca", + "blk.14.ffn_up.weight": "b006446769f51e4f93b503c4727deae897bc1fc7f4fad49f85024b63c4548d38", + "blk.15.attn_k.weight": "23bb70f9035356624039547a603e46be7d1e4403616eafc2451cc09c5373d522", + "blk.15.attn_norm.weight": "718cb371ca052eeb3bfac6ac506abb887df125271821fd171797a7f2d8dd6313", + "blk.15.attn_output.weight": "c76a2695a204b43a8e5acfa5720590b5d449a9ad9e082cbe3e80fab5903ea16a", + "blk.15.attn_q.weight": "2b3e4037b9e91bdd26d6e8d904cf39f948192dcf09bb6445cb55ca058d4f4626", + "blk.15.attn_v.weight": "7c15e89b6acafc8619e86aa9d412f5893ab17843ff2cfaf40eea9637b24910c6", + "blk.15.ffn_down.weight": "e16fd4bdc6d1c1209c6b633454df4992870c8cefb2cb0e8c92a7e489e9fb5d19", + "blk.15.ffn_gate.weight": "95a46bea366c260337c537fde06b4cbeaeec52484a69c3390bb1d178eb0525c9", + "blk.15.ffn_norm.weight": "37730293f704da265dc6d1896b3be00c39c0a41dab07f573af39dc30a481d623", + "blk.15.ffn_up.weight": "ba74a199da2d0875d7410824238c4ffafbda3993568812284a72b8800df91f15", + "blk.16.attn_k.weight": "f58f79a2a91c9a763adefce0c53a71eb5ce6bd8442f4af554b04b58083bff27e", + "blk.16.attn_norm.weight": "0c16e41b95e81978e0e0e3b338e2afe2d297426578cacee94de15df74e94eaad", + "blk.16.attn_output.weight": "ead22fc337514e4add49aee19720008558e52090466866e849671953a1fccba4", + "blk.16.attn_q.weight": "ef59c4e8fe8918c1add43d7e9c6fb3ef799dd3e1bdd731ec7b6a4a6f97c86048", + "blk.16.attn_v.weight": "902e6b84c2b64241470b13e6f412f859f66b4b223bcfb9c15d5cb1106b07ef3b", + "blk.16.ffn_down.weight": "2ad6e9eb4d8372c32a554395d460d17cfb02d6dbcb757cc962b6bfa36db4f5ee", + "blk.16.ffn_gate.weight": "825b2d50fcce3dbe6a5d8d8a50a95466f83ca4a10343efe67894c20b4628fb15", + "blk.16.ffn_norm.weight": "3bf6ac90befb0e17e077c8ea9454a8485a30f89f2d761ec7751b60c90aed1af9", + "blk.16.ffn_up.weight": "9fbdd08739b32411f5ab0252174d386bab19eb0b17884862f760429b7d41d78c", + "blk.17.attn_k.weight": "4033398718bf3674830ed1b73071ed8482b6dd4ef27f31a6c5fbb998321b6c07", + "blk.17.attn_norm.weight": "714f2e8ac9592966a0f1c02ee979eee8f84586405b992e8ee9543e840199ffa1", + "blk.17.attn_output.weight": "b6bbb618597d767b8f535117be68f92911e4a71d4eb4d8b5d943444151445ece", + "blk.17.attn_q.weight": "b84a0dc00ceb515faa2628125dcec502eed923077b21cfe900a4ff16c2e5f9ed", + "blk.17.attn_v.weight": "4387c7d6a17da9cc7a6bca8f4a75618b20407d570792056283a8e93b6ec65f18", + "blk.17.ffn_down.weight": "47db95c6f1e12b399c3eaf9ddba261782dd71173dd163b52af96541cf87b5196", + "blk.17.ffn_gate.weight": "59abaded0aedfd12f01df81f7a811e84db6a227f51b60abe9a247ca726e87392", + "blk.17.ffn_norm.weight": "b7e86445be5c7b722e01ddb98d5c7527ca86cb827ce0354f2c269e0f2558751e", + "blk.17.ffn_up.weight": "8e31c293bac649d2f60da4b3fc4a3acdce1111ec6058d8805eeeb242443011de", + "blk.18.attn_k.weight": "5ce762ab7b032511c131df81093b587871718c7097f79d8e07d707571f18a47b", + "blk.18.attn_norm.weight": "1f52cdc7af1f4dc1f0ef6ad1ad02e18cda32133654e57cfa9c72ada9c0b1d995", + "blk.18.attn_output.weight": "6486957f30bf8a88516e25772c6650f98b13923f490a2865a8752e36439d1cfa", + "blk.18.attn_q.weight": "93621c8abf69d2ca29c5207180eb628fb2b544d89de6c4a7fb0699be95534899", + "blk.18.attn_v.weight": "11604083b5a74828ac1d226af015ad5dc0215a1fdca44fa7131c2163c02d8156", + "blk.18.ffn_down.weight": "8f9997feb94385f106915df810239c9753b31efda2bf14bdf18a9fbbeec8233d", + "blk.18.ffn_gate.weight": "427c213b3a4e94af703429daf2f65766f70424d8230c123e7e712a18bceb5ecb", + "blk.18.ffn_norm.weight": "c45d305c4ea6a54013ba112f12dafaade064a32cf01317373464a3618d8ba44a", + "blk.18.ffn_up.weight": "a2811f2e73ac9eb9cce91a21a454e84e230a155244e2cd73f2c12aad3c9b8cfd", + "blk.19.attn_k.weight": "b2daed159925eac58c291e2f1e2000beed21002b03c9e1bc7e7a52e22240666c", + "blk.19.attn_norm.weight": "6307306ede2ab5bffa1bcac3f8b139354678c0376b1d9f5530c1fcb4268cfeb4", + "blk.19.attn_output.weight": "ebb98218b2a9c84d3fb6baeb02c5df264b7ab80d994d1098ba1cd47aa398effe", + "blk.19.attn_q.weight": "4f10df2ad09177e7528e9456039b670d07db22940a49417101b725d239c16724", + "blk.19.attn_v.weight": "30f1efc5114badaeaafa91fa466dc7fa14b1616db433c6f563ab851f7333a5dd", + "blk.19.ffn_down.weight": "be5ec7fe6b48855cd0015b0e430d1b70c620de87a7ff188c7c1afef546d7b6bd", + "blk.19.ffn_gate.weight": "10dffea4213881f8a9b583ee0fd370e033756d32255ed15053f794375b9400e9", + "blk.19.ffn_norm.weight": "e75cd24ade45dca78fdb0cbcaaa2d4a17d83a5a73dcc94ce0ec2d68fbdb2a881", + "blk.19.ffn_up.weight": "63e81bdb951410ffa81bcfba1b94a679ec9ebae59cd1623ce2651ed5d4c78bfd", + "blk.20.attn_k.weight": "c2fc5ad39e9bdd45e73c6e54aecc474388d944c4be1ee1921b7fcd035bad02e0", + "blk.20.attn_norm.weight": "aaa9169171937bdce20c1f057e94e9252f221cabacf1ced12e11b9586f23d308", + "blk.20.attn_output.weight": "a9f4fb496e4bc053e3f6cf2e72e22d4cd2b545ef6c32f7e782c2ef6ebcc21d4b", + "blk.20.attn_q.weight": "5a07ac619ed251494170b213921ef3fcc4c2712839da262516d9d5b8ea1ff185", + "blk.20.attn_v.weight": "d6689473105d241eacb17f09f06000ee237336916cf5ec4f48271c5b41bcb8e7", + "blk.20.ffn_down.weight": "74be38db51df736f26ede7c6b52ea787e385f181cb66231e2cced4556a25c9b8", + "blk.20.ffn_gate.weight": "ea91e06dc3d051c0ba0243b5a8bb40edbf254eadfb54fda7247e05cfdd88cbe2", + "blk.20.ffn_norm.weight": "5fbd357b3d6f44a7a91e8a4fc246b24303891b7957e0f3c32818ae5dc16ddd8d", + "blk.20.ffn_up.weight": "fe3290333e056af4ed12942ac72aeba97a6b562e2db05e79cd35dd07eab5b101", + "blk.21.attn_k.weight": "201ec6ee95f06ea5eb80fe86fd07bd016d3ae9ab6abd25d631834414e14a010e", + "blk.21.attn_norm.weight": "ea8154f93e06485828475a00b98cc397ac84768dd70e06ecc0c075b5712d7276", + "blk.21.attn_output.weight": "9f8af74d531478fd304723fd8e4e01578db598441b80dc7c960cb801dbbc501e", + "blk.21.attn_q.weight": "277de9953a8d3cff894ffd06c15ad0ee1407e319df0c1a693d4f45fa9c74ac7f", + "blk.21.attn_v.weight": "6bfdc16cfb898909b7788ddd39dd04b928f31d6732772195d53c558004638dca", + "blk.21.ffn_down.weight": "173877146cb94801157796ee9e5eecf3f46acb3b5e797f90b83a3fc22395eb30", + "blk.21.ffn_gate.weight": "53146713e2ca1be80496024077a028f6b6d749b02e71003c349e113b436f48f4", + "blk.21.ffn_norm.weight": "b28b97e18ab20a5c553ba422f7d7f6014f5902f1d62a69abd20d9fe19a5f9462", + "blk.21.ffn_up.weight": "5c39d0ac4d602b8ec8909dade93b2efcd6b6d9d84a19b252d76bb66dcfaab87c", + "blk.22.attn_k.weight": "01f26272c82917a87a3ccf922fa1d521a952b05de878241b7efe3525b617ac87", + "blk.22.attn_norm.weight": "5ffc96249d8873b506e9eb7158bdfd07fa1429e53c1951430ca7505d25f11c76", + "blk.22.attn_output.weight": "9c2201569358f720244b9c9497e4da02585a167b1414c8a506b85ad75ba990d0", + "blk.22.attn_q.weight": "906036eb4ddf027f6d920f9356a6a2a5e529b96f4e1231a0496d46b4434a5842", + "blk.22.attn_v.weight": "30ede8b0d166003a4b8a81fc99437f557719fc36e5c4dd510c9f161f36a47e73", + "blk.22.ffn_down.weight": "d04c164beabab30e1837b843e18852260efccfbb9d96a34ddd816e6fb3ba23c5", + "blk.22.ffn_gate.weight": "19c889db6b19179f0a62d5981a1506592c65de83760d67afbe00d202202750a8", + "blk.22.ffn_norm.weight": "4885eff2d851b32dbd306bd632c725857e6d164f0fa8b3d5857e572e6ef98ee9", + "blk.22.ffn_up.weight": "365594d8db8e95cf87cc33ac23947942dc326110175cc8ec5a07b5c7059089a7", + "blk.23.attn_k.weight": "badfea1569da0fc6ab817c5727ca3a69b07d9cfd622fb8be5e66678d5b3f7ae2", + "blk.23.attn_norm.weight": "8968f78a379ac3ca5458b4ed4251e8d9112aca6d6dd1ef6440b4bb0b380375a4", + "blk.23.attn_output.weight": "93e43393c03956287b1fe31e9735ff1cfe84f4ae56b83dbaebe96275e4e11831", + "blk.23.attn_q.weight": "aaff73c725a8700ae66bf26ac8869dfe96738eff23a8ff340de2ab53400a5795", + "blk.23.attn_v.weight": "3a86a8dcf14a746ed1411f5a7e634064bc4dfd6511c24cfeccfb2c9ebb6b4101", + "blk.23.ffn_down.weight": "d4da6f37bd7ef69bb203f7b0dd59f50bce37432c70627e6cf274ab81548af5cf", + "blk.23.ffn_gate.weight": "5b6072936c4a693923bb4e3d1473fd45545cb02fc07799aca458ef0449a04061", + "blk.23.ffn_norm.weight": "cd76e37025f84773180298ddb15e0d4ba9cfc7d832e19c791049daa47c6d9c10", + "blk.23.ffn_up.weight": "cde43b99b83124a13b2e4753d12674b3a61dfb34c04703007ced3e8e2aee1801", + "blk.24.attn_k.weight": "457379edc4cce4cbbe107385079019bc922264fdfc7bd1d1ae84343a81460c66", + "blk.24.attn_norm.weight": "0ce0dfab2edeede5da419fa7833db78e36222cf25c358d08f3ec664310f031fb", + "blk.24.attn_output.weight": "0cf91c2fd40c204d2fd4b9c85b69281e5ad4ea8442972fcd44b5fc8e835ffdf8", + "blk.24.attn_q.weight": "87ede30c09eafec6a4e6285674c1bc4637140b168b2da4ed34f36fdb6e176cc9", + "blk.24.attn_v.weight": "4c0b078b2798ca35d6d2c2258fe499820d2bc88700654ba4016e4b028f563590", + "blk.24.ffn_down.weight": "cdb8540c32b1ab988f984484928d39f6841f2131c1cebe90ad9456737fccbcaf", + "blk.24.ffn_gate.weight": "da2e0e913648b5526bd2bbb344038dd067639343aed3b413662b064b0db7556e", + "blk.24.ffn_norm.weight": "8940bd781c610d75eb2be63cfc8d869a3af05e53c963dc7fd4c6f653df5a80ab", + "blk.24.ffn_up.weight": "90cbac2a58801abe11ed6c24560aa4acb949f79429f2aa8ff129ac05868bb87d", + "blk.25.attn_k.weight": "90607131e36998e990ce718ad05cbecd1bcaed010931401ce6baa3b0d93ebce6", + "blk.25.attn_norm.weight": "fbf679c85656c04a6cf8fedd5412c1ace22960e6c2d47f2d43997827811fbb97", + "blk.25.attn_output.weight": "08412724ee7a2086514406e6f68fb9f622e10bac25b0c373b294709f4b09bd2b", + "blk.25.attn_q.weight": "9c1238e98a2747654a0d4371d3e7ea8b979867f609dc42482544f25591e85c7f", + "blk.25.attn_v.weight": "a57796a535c6cb09581cbafd6a91dc14adc8cca2a2465a7ffd0aec546cd84074", + "blk.25.ffn_down.weight": "f7e34e8a6391b480da08b52640613ccadce268373934b409759743a1735b74d6", + "blk.25.ffn_gate.weight": "b8d0b2f4612678b5ce42bd4a683f8024514b75fb5ebf6b22c600811e95582ee4", + "blk.25.ffn_norm.weight": "cde1fdba2369d315f3c6940a997c471ec891924e642505db580d732763bd7b75", + "blk.25.ffn_up.weight": "72e700c32ac8b9c47559c2222e45888a480b527ea512075423c5dc01678e2bb3", + "blk.26.attn_k.weight": "6ac83b3414ae75bf3a9055c32e49d2c40fe611ab21f8444f03d2f465d18122c9", + "blk.26.attn_norm.weight": "55f9d6dc9d75973dc75136ecb9d991b4398097ac133070873fb96ec76a6f60bc", + "blk.26.attn_output.weight": "ebc4fcbd15b33263e50ed2ad45740867cce15bc90e1216623babcb1820734509", + "blk.26.attn_q.weight": "080f057521073e412936fe3fee64fd574c8128fa4a148b879d3e598fe4954581", + "blk.26.attn_v.weight": "0fa2830d6746487ac91b243716e4302361f891e4e008eddd14abec47c7809d5e", + "blk.26.ffn_down.weight": "cb2ab8af1653adc57111ada49d2825c6995e338c8208455b92de10e580f60f31", + "blk.26.ffn_gate.weight": "231ce30966086bce2dc0e0afd34a22a1958cfda7a57c41b3b8e9444c5dfde8a6", + "blk.26.ffn_norm.weight": "35d959d25d17b00617590f5d5831bf705c385c51e46297a14375a700effca6af", + "blk.26.ffn_up.weight": "367680c8d332538b467d1ef87cfeb36cc5c6af564c5023c5fb50e728e3438287", + "blk.27.attn_k.weight": "0bfcb351c6d17aeac5b55a915074fbdf00f11c4bda98babb196ac8804805746b", + "blk.27.attn_norm.weight": "5d598a88c2e75ba59dd7ba4fee940bdec92d72038f1286536d2dfb71d008a09c", + "blk.27.attn_output.weight": "23a9da7347336479f6a10ded14cb3f46e06b5bd56dc4b0fbc526c688552ec840", + "blk.27.attn_q.weight": "b83319dba9055f069208e9c9d66da08bc6874f23e575288fcd81697d1777aa54", + "blk.27.attn_v.weight": "36ed34ccb2f36fdf16b2c2dd225a98ea6b7b0e376e7791191136ccd7bd7a4add", + "blk.27.ffn_down.weight": "5488e1d3a58c71b5e9ddda430540b4776b268cfe1457cbc1c2622dedd9e4526e", + "blk.27.ffn_gate.weight": "4ff48011ee0bac39af704849d9132a2410392c87a509c684f2062f6b76b498fb", + "blk.27.ffn_norm.weight": "32afe99675983da3de2961d1b5ca41c98970a356823597fe29e91f6e86abf0e8", + "blk.27.ffn_up.weight": "1eae3088a75629571fdbf6a20f141bc2bb2ed3f5ba2b9fd1d949f80695e442a1", + "blk.28.attn_k.weight": "c4e80af714962d6f9040d2c09f316f4a1cbc3a2e994e19902d7c653cf3c73dba", + "blk.28.attn_norm.weight": "c1ecf85dedc1c83d5d402bb7c94fb8b9c11f1a3e5f64e7680f80912d4a560794", + "blk.28.attn_output.weight": "72ba47c061b21f5ebc5213a455eaf6fc49c8f8e04ff9ce37e6ed4921b629161d", + "blk.28.attn_q.weight": "c4abc47234307f44b8ca789aa6668e298158fa4b459b2c1e84bd581806591cc1", + "blk.28.attn_v.weight": "aeba950799d4950e491ad0fcbe30334e39b8975177990a2cb339031c45ac153c", + "blk.28.ffn_down.weight": "4e84ce382a37b994fb8608df451a60040559e3f4f3241c3b3cb8989a3ed50d83", + "blk.28.ffn_gate.weight": "04df157acdc8e8534ad60acc2d2a4dd3a7a6610f6382535ec728994fa6f83f83", + "blk.28.ffn_norm.weight": "4d0386dae2bd1c1a9d0f9730718333e3a486c3bc6a5c5d482193c75d39832c80", + "blk.28.ffn_up.weight": "fec60bb0a3daf182a14bd8311fe6dd1e3fd020c5fc273e2549cdb1a2d6b79b05", + "blk.29.attn_k.weight": "b0532a263aa5a4e2a7a80adc83fc5dec974493bd18da7f953e7ebfc3f3a19aae", + "blk.29.attn_norm.weight": "593fc3b4000c35b7a59dace09ca1756c08be0105b2edd354a0e1c16c82898859", + "blk.29.attn_output.weight": "315b896f9f0cbacd0ca8937384c3a3a227efa908cb8c3a9125ec00c480e32b9b", + "blk.29.attn_q.weight": "d482d45386d4ad3394f08e9dff233ee3a70d0427d65c0b8fa05905da7e25ca53", + "blk.29.attn_v.weight": "cd3b5a6e2852da796902930a6a84bc87fc6a7c7bf51f8fc23758d12a39013b36", + "blk.29.ffn_down.weight": "5b3dba6f9753bd1b1ebcba65ef5373dd62c38e755c44b7231b95d93d45761f89", + "blk.29.ffn_gate.weight": "8610d9d2db15c256243ffcca3ffd31786d0ada0af0e7c7aa3fd20524370ab036", + "blk.29.ffn_norm.weight": "1a2ef2d38b7ac3e51190b9ccb8b6552ba83ab290e523356a7f851ddb35dedca2", + "blk.29.ffn_up.weight": "a5fdd15811bde16dc27677cf1a4c97daab4c28cb12a9530f1a0e573134fdb69c", + "blk.30.attn_k.weight": "1efeb0b5f4b45a85cdf47300f892ac77ac1f38000ec3653565d1303d1fb8c743", + "blk.30.attn_norm.weight": "c73934c182c7fe80838ec1d0b92f50a583f75f7a3d78d822f009b58ad2c80e65", + "blk.30.attn_output.weight": "3a0fd89de2d274614750345d827a9c886a4f97b343a13cdf680390505df596a3", + "blk.30.attn_q.weight": "711e113362bdb067db843c66236704eb1cd3fc5f40e3767143e96d510686ef4e", + "blk.30.attn_v.weight": "82b12a9a74fd3d91b73cc2e841e2b3f0a5197ccd2998afa17020995f880d2267", + "blk.30.ffn_down.weight": "af9f4b1287c0d824ae22d6e335d19e04a70135b835be7caa2435f1d85e931993", + "blk.30.ffn_gate.weight": "e2ab3e6f15f5c50fca66c084cb6a57a2b6b82406d65150e82ea0437b93dd9a46", + "blk.30.ffn_norm.weight": "c1b9c325c83f00e177386a4d7e769945f2995e60950c4a576c0a2c4ab9703d04", + "blk.30.ffn_up.weight": "9b94a21efd419715d82071b490d3b635cf1e8da080620dcc39e5bde976d7e9a6", + "blk.31.attn_k.weight": "0db0d82e3ddcc2c06209f5f013e1d72a84a996c40bf00186be485b909cc268e8", + "blk.31.attn_norm.weight": "2b8b7239471f57140c5cdfe06bd224a4f6326282f99736e44fba4c7b120ac101", + "blk.31.attn_output.weight": "a310b048840cc3ff2be4b84796340e8e2cdf05ec89d14bd3655c109b2bfa9fcd", + "blk.31.attn_q.weight": "f45e0cd95645175ea82813455356d171838539bc3f7676d877c698f2af0a0eda", + "blk.31.attn_v.weight": "8bde008e809112aa7e7c23e9c3099087bcc557313b01306c87efa0a4a30805ba", + "blk.31.ffn_down.weight": "8266fec7e203fbfad7033120861e44984581ff8b6851d01dfb7b81c5d8fa90ec", + "blk.31.ffn_gate.weight": "b73bc0aa5baf006d9ef6403104891b8133671b0992398fe038380b67e0d7e2cf", + "blk.31.ffn_norm.weight": "9c62cc27a7b6017c1df8ad49bff249a8245e8895c6754f402cd44623fda83268", + "blk.31.ffn_up.weight": "5b970a4694ea3171a0167f6e1636d9f00268bc1c9640430ffc35218494884adb", + "output.weight": "74fa0ef08c57a30e633e7117b1e9c805f833e2e5e21434bc79ddf9c92c6d7330", + "output_norm.weight": "59b8a59fd3fbf39353506116e43e5e76edd0cbf2a2873d869da4cf27a04997c3" +} diff --git a/convert/testdata/Mixtral-8x7B-Instruct-v0.1.json b/convert/testdata/Mixtral-8x7B-Instruct-v0.1.json new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/convert/testdata/Mixtral-8x7B-Instruct-v0.1.json @@ -0,0 +1 @@ +{} diff --git a/convert/testdata/gemma-2b-it.json b/convert/testdata/gemma-2b-it.json new file mode 100644 index 000000000..0482f1e14 --- /dev/null +++ b/convert/testdata/gemma-2b-it.json @@ -0,0 +1,188 @@ +{ + "general.architecture": "gemma", + "general.file_type": "1", + "general.quantization_version": "2", + "gemma.block_count": "18", + "gemma.context_length": "8192", + "gemma.embedding_length": "2048", + "gemma.feed_forward_length": "16384", + "gemma.attention.head_count": "8", + "gemma.attention.head_count_kv": "1", + "gemma.attention.key_length": "256", + "gemma.attention.value_length": "256", + "gemma.attention.layer_norm_rms_epsilon": "1e-06", + "tokenizer.ggml.model": "llama", + "tokenizer.ggml.add_bos_token": "true", + "tokenizer.ggml.add_eos_token": "false", + "tokenizer.ggml.bos_token_id": "2", + "tokenizer.ggml.eos_token_id": "1", + "tokenizer.ggml.padding_token_id": "0", + "tokenizer.ggml.unknown_token_id": "3", + "tokenizer.ggml.scores": "0872465d173867d755d3ee728f882b9dc2057a0bfd596fe1e3d131522f1250d8", + "tokenizer.ggml.token_type": "485e40bf3d715a4764818fc097d6a2a41db872d82ee714bc500872a3437ff48d", + "tokenizer.ggml.tokens": "c6e66de1841f04de8b8d236d461ab720a4c9b9b5414dc293a09c6e10eab45fda", + "token_embd.weight": "17b87ab2c01c80657855a5413d0457b4a041afaeda0cc785080e44e2f04acf07", + "blk.0.attn_k.weight": "28ac0da05754ad2714ae95da28a5ad191192140b30b8fd22d108d4700c9d989f", + "blk.0.attn_norm.weight": "3f9d5675d1ab0eb8a816719dac9fab81f2e95c52be02c34263339acbc087febb", + "blk.0.attn_output.weight": "703295c2c63990ff896778685c678f145298886f680f3ed5dc2a7ad54c293265", + "blk.0.attn_q.weight": "69c2d0e4870e9d722a190d356203c9605575a16863466c3d1747966ef1cf5791", + "blk.0.attn_v.weight": "95219c9c07b5ffe9a9a01e456d845eef2b11f4fc12c93dbbba479db395444c13", + "blk.0.ffn_down.weight": "a2feb5eb3d572c57c5bafbf0ab506862df1160fe40965dcfe4b9fd855c08bed7", + "blk.0.ffn_gate.weight": "fcca072c445c31f4dc4d5dfaa785b1bdf7271342442099b74fd17268b5829fbf", + "blk.0.ffn_norm.weight": "7621f95dbd245cade6fffd6b08797d69d8e3954e960f0b5551b90d967ab95448", + "blk.0.ffn_up.weight": "14a9bcdd451403c67136391e1b6e53b3b1830f00199bd911dbcc56d8749c14f4", + "blk.1.attn_k.weight": "c70f73c5df20579cb44d971164b48b5f0d8d5abdb38b381e7a8b880ba12aa406", + "blk.1.attn_norm.weight": "88b6b91f93a1ef83425a7c7dc2a2fbd3b22704a04c64a80061df376ac8c33626", + "blk.1.attn_output.weight": "f031a537490c452be3b3bb51e6b7949a636405756e160976a1c070a792ea00ee", + "blk.1.attn_q.weight": "bdb23214b1cf9cfd30f863a0a5868e52c6809d93b7e8f44df096a94204d9896a", + "blk.1.attn_v.weight": "e9bbc0b05f2c872fb1403f8f938cd1612b502229ee401f12593b1164c61acc00", + "blk.1.ffn_down.weight": "5ff53811038b661a7b8f2bfdf213bebfb185ec1a6060b662f063714f33584d79", + "blk.1.ffn_gate.weight": "205085c8c951a5c7543b1495183cd96028fb49f67464b3e9862a2693a6077a33", + "blk.1.ffn_norm.weight": "798f354fc85afce9625f5d10093a585a966831698a0560e6c9b97ce659eb4b22", + "blk.1.ffn_up.weight": "db92dc5684cb6e90940e13f4d1da555ed20ba4f8cab1e990ddfd7553e2e91315", + "blk.2.attn_k.weight": "ef5ce360c4eed6d00d03ca4761e0f8e4b0af4509978468314be14f3d46621044", + "blk.2.attn_norm.weight": "6dadbc05dbd0d3fabb4216affa60a3de1378a82d2859dc90b338cbe70f50d455", + "blk.2.attn_output.weight": "6bbf87a966f691bbfd7c8d25629aa4e6710107bd431a667434861febb391edc5", + "blk.2.attn_q.weight": "4e575c09ae2de417ce9057ce8b073680e860a24aae13a472b68f101b760752e5", + "blk.2.attn_v.weight": "cd33f7f01141e9439afdaf2ea1aaced9feaa335e32a58daa136ebd555d4d96f4", + "blk.2.ffn_down.weight": "b970ff1b0b6494165defe2fbfa1d31425766ed71e64de9ec4e66ac3955c8bc5f", + "blk.2.ffn_gate.weight": "dbb3e1360402e0e369b101995bb686b73f95d4a7673f061be85d64d15dfb0061", + "blk.2.ffn_norm.weight": "bfb7980105d8ac9647710454f57a5cdac50598a0f6f4884e16f1d94b00844687", + "blk.2.ffn_up.weight": "50ef89339b275a438b664686f6227dd9b6e43853ed6856ec9e33ef4bbd90bda1", + "blk.3.attn_k.weight": "be942ea98151434eebcd2c1da4b00e0146152fe524a530689b1fd491cb833d21", + "blk.3.attn_norm.weight": "0df2f218daf609c289fb7c60c5f375fa99c0d4e04381ad5a494a19144edd8e20", + "blk.3.attn_output.weight": "c2184aaf86aa2cb8f47be49f60b165834e97205f39c6ee1dfd19fd4411a156ce", + "blk.3.attn_q.weight": "4f86e2a0a4221c1c84ff9c409ac89893cb95d7208cf65bf1e98e24e01125f991", + "blk.3.attn_v.weight": "abfdb8a60c349dadde641d1afc9542025e24fbf41a3238bfa9675e0b1f1e4b68", + "blk.3.ffn_down.weight": "58821a8d87008d47d122427911c6fad5272aca70c448bbae223256a74bacd07e", + "blk.3.ffn_gate.weight": "776e051f1a0ddd5c4934e69186683a75ca9a3c8c0f61911bba321fed1dd287d2", + "blk.3.ffn_norm.weight": "7f380f29335e28be90bfcfae6f6d69fdf5751211b36d2dd62aa5541ed113e4f2", + "blk.3.ffn_up.weight": "fc5ae8d488894cbd4951059675468d227da27871d26e925c9941863841c097ee", + "blk.4.attn_k.weight": "14833b078cc4c5137bdd5fdc0538047974ca147a99b0282e1b144440c78bc1db", + "blk.4.attn_norm.weight": "0a69957d4a15599fb80ad4753558020804925221457d9a5052926754d3768065", + "blk.4.attn_output.weight": "887a49b6130fb6297cf10767207c3dd97191b2cf63723449af9c27bca8dbeda0", + "blk.4.attn_q.weight": "51fd577b76764824dd6f0d4891c137ebe4736f591b5ca2793c5fff2be49abbde", + "blk.4.attn_v.weight": "1a623c43cf9c509d1b7ea0d1a5c04d0af4809665f9f9e93b7d6dba8c5df178fa", + "blk.4.ffn_down.weight": "5d61e8856d8941d2b1fd138116d015f63840d0fa1e31e20e20a5ceca1536ceec", + "blk.4.ffn_gate.weight": "06640f7273764f8ca5df7e386547417916b6cd7d565a8343153113239a94b0a1", + "blk.4.ffn_norm.weight": "91a6c6c41b894228e361435ecbc5058dca34d4911a23da5b56de219299c964d3", + "blk.4.ffn_up.weight": "d016dac1055e36d6a10b6317e57f98a904709ea892ef3194342f4d2f6326561e", + "blk.5.attn_k.weight": "987146afe124131500808cc0da33c06d207433656d41df6e6d8c99118a83bac5", + "blk.5.attn_norm.weight": "6b354938966f2608a2fb8d0f5b363ed0d8b0967c2ec8d0abd5c625b413042ded", + "blk.5.attn_output.weight": "cdcbfe02c6ff79d5326882b017a02099f5af71beedf6b1b3eb4de01e3a844536", + "blk.5.attn_q.weight": "b910d0cff781d3efb42eab0a302f46f286b2de717079175680d5b42bf8c309c8", + "blk.5.attn_v.weight": "66d3a279f747412f9f4b0e8abad44540c122ab2e811a7ee74c1f33bc36caade9", + "blk.5.ffn_down.weight": "c9b0efd2212981f16d956d8571f054b68780ad01f4917033647e359b557a4653", + "blk.5.ffn_gate.weight": "fe96b94109ca141c01f6a04788e20783019ca6ec334aa1f3134810bdb499e557", + "blk.5.ffn_norm.weight": "aa7b016e832e7055a36c6e20de58ea1936f995f390401fff1c5fc65906064e49", + "blk.5.ffn_up.weight": "555ce27c4873d3375394f38ad3b45e3d8848f9d5642dc1602383d0f0a33c2a14", + "blk.6.attn_k.weight": "88280d461db324c4f36475ce396793063e61a27283ec64511b0480890fb5b3b4", + "blk.6.attn_norm.weight": "af8f460c411f660d33196286d208f1845fd5a2b45f7b56549a4df31e7515447a", + "blk.6.attn_output.weight": "dd9996fb0a256e8375ad3917705258a33fce006bcea0f536caae420a77974d8b", + "blk.6.attn_q.weight": "7a4841541191e037cfb9b07930c4d8cab451809658b182f0ada6ccde9615c003", + "blk.6.attn_v.weight": "ae81e6a592b64d701a9d40233e986039a56cba8d8d24f61aea93c6393cf3078a", + "blk.6.ffn_down.weight": "622dd1ce1706355cbc659a8ab2c4509678ffe0f3ad34258e5e25ed2a5d951bcd", + "blk.6.ffn_gate.weight": "8389a735c0bd5591010f8ced9805a2a12c749f6df0d3c18ad4d05c2a302e7168", + "blk.6.ffn_norm.weight": "621f5346400382474d61358397bd58fb1459b07c53e376e4bca15e08b3f9b3fb", + "blk.6.ffn_up.weight": "8d834e4c42f13c251dfee36cf89e12f1bd400680d00d5c2e6cac0459e9ce2f7f", + "blk.7.attn_k.weight": "8bd0412de65a3e64901ef8fe6a28c95e116bf39dc9aa22f0126b9d36688e5ea7", + "blk.7.attn_norm.weight": "056d8e56be4e87d6dc6f900762f0dc6fde07bfdc50dd85bfc510415e2bba3f3d", + "blk.7.attn_output.weight": "27972eda51da53d416ff95aed78149a2c5a287b47d2cd46f2f544ca692ecb3bb", + "blk.7.attn_q.weight": "41eca977b9371f7932800c11a9c45b931310196919e2a0651b847703b180fc7f", + "blk.7.attn_v.weight": "13c74fd7e07f08883a09fb070a1fe5bbdd2341b4cb8d1cac07c4b637049b5774", + "blk.7.ffn_down.weight": "9e75db42468800849a9a7da603d0072c5e86c8ed2b4d8b20a312a51fb86a7a10", + "blk.7.ffn_gate.weight": "db6bdc3117f910088aaf7db51f2da63ea5bd933de36af5599c215bfb26f7db2b", + "blk.7.ffn_norm.weight": "48bb82b49bfc8679a1e77f282ee182d952db7a3c11be7ef9a102ee2ddd8011e2", + "blk.7.ffn_up.weight": "feebea87175817a0f3585ec0af09dc873d94c203581ae97a712eb356d3b49efe", + "blk.8.attn_k.weight": "d5640ad71b6af68d88e17bf8e7fc26c907d2262605457a84247dd9afc2884d69", + "blk.8.attn_norm.weight": "75b850c481a69083ae09d0207ba7317b37c735a39fcf5fef5400e6c84fb1257f", + "blk.8.attn_output.weight": "cbd669dbdea2bdd90f9f0cc97566b3dffff3c56cecb4f47290ceef30da83b2d6", + "blk.8.attn_q.weight": "9edcb63087a431bac361822497e6ecdaa06d9ea4a1a754e36da7ba9f8db81c7c", + "blk.8.attn_v.weight": "3fb72c2c4f95a83626aa3e30062f9450b09ab37c7871e229f18bbc5cf744633c", + "blk.8.ffn_down.weight": "bd69d2c9172974fff154441b237b4787fb53b2d185325442d5048130ef5bc4ef", + "blk.8.ffn_gate.weight": "d04689c80553edd011d1cbaa5d570fffa7fa91e88b66cf1352d89ab60b72f908", + "blk.8.ffn_norm.weight": "e49984183b735b7f2c4e4730c289eed9394056d2e283a00fd83ea0915df31a73", + "blk.8.ffn_up.weight": "8fe62a1ce8e847e567add6c6f6bf2922bc467495b5eb4c116b3cb85b85b3b211", + "blk.9.attn_k.weight": "d90904959e5004cf0d6e729c6bff18cc33c094798b802473c1ec55ab8d276183", + "blk.9.attn_norm.weight": "79277f290cc07411115d8fa138045edf4a17b3416ab2145409cbe8ab829fd4ee", + "blk.9.attn_output.weight": "5a21bf2e1f09a81405025f96d4153ffb630158e17269cff8ffff935c38ceb1a7", + "blk.9.attn_q.weight": "51b1d0febc3b350945be4504f55afa4347517bde0f710e1a4b88e6b17e71e7c7", + "blk.9.attn_v.weight": "aab7e1db0a8b50a03036356791ffce736ab010d15674c96eaef8049d80076054", + "blk.9.ffn_down.weight": "cbf43ec84becb40c9359a181ab0e641fd7faae7d34b549501f7cfb7afdc3d764", + "blk.9.ffn_gate.weight": "dce0e8661c778327bed7f03b6790d26710764188aed9dc746e6e05863891fa57", + "blk.9.ffn_norm.weight": "6d41642104f995c77bf31122b13237caebda3e7fcccb1367ce91db36b015e923", + "blk.9.ffn_up.weight": "82fe4c67bf24e7b2d6f6e05f7b1234c2bf90c3932951091a9066211b8e15ecbb", + "blk.10.attn_k.weight": "f6a9ed8fd8d3229b5d03175c413ffc56a07f2ce7236271986361dd3d8993f9aa", + "blk.10.attn_norm.weight": "cebbef89f0326ca8e02df3867a571e4d61c20c2a12f295f98ae590d62bc86010", + "blk.10.attn_output.weight": "34f5efb86accb4f06347d83a32558ea8eab3039d128969161a741ebacbb656ff", + "blk.10.attn_q.weight": "1e0efe27df2d5d50f7157253ba2cfd436d6781c3dc78ca176d0c16a210b5b763", + "blk.10.attn_v.weight": "8f085bf50a2b0f83cd6cdda3c8ef5a9e204a36348ed95871aac725d1f68640cf", + "blk.10.ffn_down.weight": "bf3b3cb4cace435809ac7b4cc933f20853af12f1f272d3dcefe7f19c0f203b8b", + "blk.10.ffn_gate.weight": "d3df7a1413b1c5adf1a1dcda9e5225a15c89874bae53bb6137ad1ea42fca2d34", + "blk.10.ffn_norm.weight": "a1da603b0480471b5ed8e862148cecd5fed918f8304d6933ab0bdb25b8d2fb8f", + "blk.10.ffn_up.weight": "bffbba605922e972dc47dda88a0b4659aa52236c76e5fe861a949e6d9a367492", + "blk.11.attn_k.weight": "9f31c63d66cd32c29b1eb8bb829d0c8525ce2ae936e0eefdaab6335a2d12a3df", + "blk.11.attn_norm.weight": "0bde1a266d8b2e8f202bb7e2e88b19147ca83021901f6d3cae77a4df5548c754", + "blk.11.attn_output.weight": "e10725c7cf746ed4a7e472cf7aea6cb564e5db6a1d5197adc980d650a387ccea", + "blk.11.attn_q.weight": "05ee758a7d065802630f8c65dca424364c1c8825e389aa33f9405c45e8a50cce", + "blk.11.attn_v.weight": "0c3ae7090f11775d24c51120db6e305db6aff706493e7ee123dcab74485ba789", + "blk.11.ffn_down.weight": "7ba40b8e12c09c5fb2006b77a771cb01ce894e88a3b3e1877f927a5b89c91709", + "blk.11.ffn_gate.weight": "db76388a023b98097972d354ba1c6a5e26efdeb1c596b9c28bf2cd8f6596975e", + "blk.11.ffn_norm.weight": "a38c3ae1b89a68ddc7b72c99c5b28be7fe3787c4fad9904d0c43d64eaf00c474", + "blk.11.ffn_up.weight": "13c8142f9cf1eddc658babf978daf3515c4ccc45f849f3e7e3930aa18a8480a0", + "blk.12.attn_k.weight": "f03241c36ac87cb57429a2ef22186b8d7d0b590a8b173beb01fa13d93772f3b1", + "blk.12.attn_norm.weight": "4568f654e6d65104d586e7c16ba960c83428698ce103022b7e0be15e2884e13b", + "blk.12.attn_output.weight": "04867603f82f91e41306e09b33ecda0104b3ee4834061f2c0bbdc8da33c72509", + "blk.12.attn_q.weight": "70fe04b9a8e08b6100cc8d6b58bf4cbbad15ca1de82d63baca5d352ba6c4cbae", + "blk.12.attn_v.weight": "15cb28db61a86c98687991d7e611bc92a1fcc6007f3432149cfb5fe518a4f65e", + "blk.12.ffn_down.weight": "6d10c790a4e3dc44c2dc36d96251ae97cdf30a4fa04d4c43e31bfbd038e6a7b7", + "blk.12.ffn_gate.weight": "3462a2d8f6b4743b25e24da51b90018ac2858d05ac7e582bcb69063cfdac1104", + "blk.12.ffn_norm.weight": "1f96392c1faa34e34ae5dea55a6a86c5aa4c79758952075d53d28de89dd88456", + "blk.12.ffn_up.weight": "d22eacc612a7411953d948483c5fb201e11722955ee0754da866e7bec578ac6d", + "blk.13.attn_k.weight": "5864977e6b733ea942647d6feed5c76156c48c200649c22e4e11b9e5860e57f3", + "blk.13.attn_norm.weight": "87e053535144723db4145aa5402acc54331b7696752d852bb9fc542ff33f0fb5", + "blk.13.attn_output.weight": "078145f5ad83f8b14f97a869346f7fd1583b24d1e3edadaa95d3da4242973f8f", + "blk.13.attn_q.weight": "3b8caf35504cbc4d1a7dd6e011a95760703b7f71e2218b030b1254f811362dd7", + "blk.13.attn_v.weight": "4fdf8365a603e043e5b40c4a21c84ac167f9be62794178f9d8a608dfe5653bf9", + "blk.13.ffn_down.weight": "a07d3abbfcacf48ba028df2cab895be32cc15022d23389a745286e79c1b1d1fd", + "blk.13.ffn_gate.weight": "1d2ab39666aa2909acc96787432a3ed13b19d25170f74665fadff9b17bbaffb1", + "blk.13.ffn_norm.weight": "4f2e809fda5f3eadf52578ee50e0ba36e53be91e55dce418c12dfe595f5f18e7", + "blk.13.ffn_up.weight": "8783d2720c2c37ca176a5801e0b3ef1f9cc9cf3ef1cd37af423aaf6b2a27e2bd", + "blk.14.attn_k.weight": "ce9428e2b55d43ae0c6690dbd56182f99adc427694ba8236b405cc8ea5035e86", + "blk.14.attn_norm.weight": "6abb35f9db8251d6ae954bda147c6ada2371b0574d11702e828f3c6ac99b7cc0", + "blk.14.attn_output.weight": "fe3880916d0ceb5bff672c88bbefb7060a545be609bf049beb2024b38221836d", + "blk.14.attn_q.weight": "7c8ad81be6f4a350931fd108b5f7c9e366e8c26ef62d1d85ffef5dca8fd893f8", + "blk.14.attn_v.weight": "e4bdedffacbebe38567a0734dfd67db90e911d9a9669fcde9a7c4ad8a0066c52", + "blk.14.ffn_down.weight": "ef6694dff1e05820aac0cd2b22f39ac7788b4967afc9250775575554c66aab2c", + "blk.14.ffn_gate.weight": "db63c4179e2db704bc505e2b4696e055b593e295a1b7c4c586fc793bdd5aab19", + "blk.14.ffn_norm.weight": "2796a62d832a9710148f95d533320492a33e712b2e5218659c548705bd11684d", + "blk.14.ffn_up.weight": "3f78c78d8c2d54df45f799d4ff902316628af296834afe4ceed63d4a324ff03e", + "blk.15.attn_k.weight": "6e810ee3859e07695645ee0c9a5efc7962668984a5f0a9325f47e462743b447c", + "blk.15.attn_norm.weight": "0956b576ae96db0b28cb09f761f801cfd9281432284664f0fe181c8d9c55d1ec", + "blk.15.attn_output.weight": "03a17f7e94208177aace5cc41b7f54670ba57873b7274ff6e23caf58cce110ca", + "blk.15.attn_q.weight": "b8edafe7d2216a6f8b4ae4905a906475490e6ea418f6e1d3cec563dbdc6fab91", + "blk.15.attn_v.weight": "f8ae8cae0f4cfa34a459824eba57350c3c248104ba5607e7d9dc7d7c39aaf4a6", + "blk.15.ffn_down.weight": "8d02eb439da852246d2ca67e9b7b6de0b090b80744355e64728a23e41926505b", + "blk.15.ffn_gate.weight": "ed5bf361c67db8731f186b775826f21c33bdb521111fd2d922539719a770239f", + "blk.15.ffn_norm.weight": "5942ca3c73209ac9a0c8bfd9b4aab7f7be7aee9aa12d9c35833493b44af76767", + "blk.15.ffn_up.weight": "f4bebf4ad99ec5f911327dec347be6c595814885309c7bc5647ce28c7f4d1cf5", + "blk.16.attn_k.weight": "756a534c19364448e0958b8948fe33891c6ccda0fbb4dfa2024e1f532a87804b", + "blk.16.attn_norm.weight": "386b7b9e4e6509f6af9c022d942b6c6c6cc136aeed8751ecb037c74d7c4bfb93", + "blk.16.attn_output.weight": "3ba1a766a25830b84d7c22178203635f9c5624caad290bc5e5d73da5d5e7a2ec", + "blk.16.attn_q.weight": "d39b0c91e1fda7685d50a0f7cc8d18c44b5bdc90a142c7fda0bc329cca1afa74", + "blk.16.attn_v.weight": "98b33fcb0ee3483cff1b06ecb44d7b7ffb4d34c268248e4d73dfdf82b2065b2f", + "blk.16.ffn_down.weight": "14006f5e4acb2f9416271ae562e299359cd2585739c7fc77ccbca54495563948", + "blk.16.ffn_gate.weight": "12f8abae2d301d8f88bedb6af98b1daecc7b0b8d05148594f931f30958d77aca", + "blk.16.ffn_norm.weight": "129a15a046ee96d06de288bd43c80f77a6b0fb3a159c7367154c6e4aaf362672", + "blk.16.ffn_up.weight": "b4a5911a45f3871ef1d4efb7dc7108645a564b70f818eccf45beebef2e844ee9", + "blk.17.attn_k.weight": "5e1bfcff0146ebdde3817b656952892eb671e14e75afc92fa53f84f8eecbec4c", + "blk.17.attn_norm.weight": "60bc988fab7c4b29ee9de599df41a8de00caa94fcd74677da011fac82f60f465", + "blk.17.attn_output.weight": "ba49b40d6a0b5685f749c24b0edbed3adc44dbe13b5d5e5fa1e56169fc746555", + "blk.17.attn_q.weight": "82bb415d24efcd14d03ace03f907bb70db6a204c76a0bdd1892e0fba165db87d", + "blk.17.attn_v.weight": "73dbe54beb91a899884e275ea81ffc5187a20cb7d5b68d5c299b783096999d94", + "blk.17.ffn_down.weight": "7c086166241e0664f8963fd1ca4ed74c737abfb2525ec20f8435821ff50158f3", + "blk.17.ffn_gate.weight": "51a32f78244d42a539f619c5ce661db9e6cf41636280a826d439b5444edcd28c", + "blk.17.ffn_norm.weight": "c4bb247fccd1ecc84875028af63dd20aaf5cbd17eb94a9bc36679c09285dccab", + "blk.17.ffn_up.weight": "b5886182790bc6fbadd63de9bc4ffee416f3b69a66280d197ab8c18edf769abf", + "output_norm.weight": "481f3097d0a20412e35b3a739b1b958487bcd41ff67744baa3c9acbddd2ee4d4" +} diff --git a/llm/ggla.go b/llm/ggla.go index 34c4f6ca3..831f60712 100644 --- a/llm/ggla.go +++ b/llm/ggla.go @@ -36,6 +36,8 @@ type ggla struct { kv KV tensors []*Tensor + + tensorOffset uint64 } func newGGLA(container *containerGGLA) *ggla { @@ -50,7 +52,10 @@ func (llm *ggla) KV() KV { } func (llm *ggla) Tensors() Tensors { - return llm.tensors + return Tensors{ + Items: llm.tensors, + Offset: llm.tensorOffset, + } } func (llm *ggla) decode(rs io.ReadSeeker) (retErr error) { @@ -66,6 +71,13 @@ func (llm *ggla) decode(rs io.ReadSeeker) (retErr error) { } llm.kv["alpha"] = alpha + offset, err := rs.Seek(0, io.SeekCurrent) + if err != nil { + return err + } + + llm.tensorOffset = uint64(offset) + for { var dims uint32 if err := binary.Read(rs, binary.LittleEndian, &dims); err != nil { diff --git a/llm/ggml.go b/llm/ggml.go index fddb50391..d7f2eef7c 100644 --- a/llm/ggml.go +++ b/llm/ggml.go @@ -112,11 +112,14 @@ func (kv KV) ChatTemplate() string { return s } -type Tensors []*Tensor +type Tensors struct { + Items []*Tensor + Offset uint64 +} func (ts Tensors) Layers() map[string]Layer { layers := make(map[string]Layer) - for _, t := range ts { + for _, t := range ts.Items { parts := strings.Split(t.Name, ".") if parts[0] == "blk" { // join first and second part, e.g. blk.%d diff --git a/llm/gguf.go b/llm/gguf.go index a8427aed8..aadfc4ba8 100644 --- a/llm/gguf.go +++ b/llm/gguf.go @@ -89,6 +89,7 @@ type gguf struct { tensors []*Tensor parameters uint64 + tensorOffset uint64 scratch [16 << 10]byte } @@ -109,7 +110,10 @@ func (llm *gguf) KV() KV { } func (llm *gguf) Tensors() Tensors { - return llm.tensors + return Tensors{ + Items: llm.tensors, + Offset: llm.tensorOffset, + } } func (llm *gguf) numTensor() uint64 { @@ -236,6 +240,14 @@ func (llm *gguf) Decode(rs io.ReadSeeker) error { alignment = 32 } + offset, err := rs.Seek(0, io.SeekCurrent) + if err != nil { + return err + } + + padding := llm.padding(offset, int64(alignment)) + llm.tensorOffset = uint64(offset + padding) + for _, tensor := range llm.tensors { offset, err := rs.Seek(0, io.SeekCurrent) if err != nil { From 0f3271db8892581f800b6f1c4a795aac4f3127c6 Mon Sep 17 00:00:00 2001 From: Michael Yang Date: Wed, 31 Jul 2024 14:48:06 -0700 Subject: [PATCH 179/384] patches: phi3 default sliding window attention --- llm/patches/11-phi3-sliding-window.diff | 43 +++++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 llm/patches/11-phi3-sliding-window.diff diff --git a/llm/patches/11-phi3-sliding-window.diff b/llm/patches/11-phi3-sliding-window.diff new file mode 100644 index 000000000..fde3dd219 --- /dev/null +++ b/llm/patches/11-phi3-sliding-window.diff @@ -0,0 +1,43 @@ +From 6eedae4cf2fcc8015dac79cb3f28f61fcabacab2 Mon Sep 17 00:00:00 2001 +From: Michael Yang +Date: Wed, 31 Jul 2024 14:57:04 -0700 +Subject: [PATCH] phi3 sliding window + +--- + src/llama.cpp | 6 +++--- + 1 file changed, 3 insertions(+), 3 deletions(-) + +diff --git a/src/llama.cpp b/src/llama.cpp +index a207451f..f2872d4e 100644 +--- a/src/llama.cpp ++++ b/src/llama.cpp +@@ -4893,7 +4893,7 @@ static void llm_load_hparams( + } break; + case LLM_ARCH_PHI3: + { +- ml.get_key(LLM_KV_ATTENTION_SLIDING_WINDOW, hparams.n_swa); ++ ml.get_key(LLM_KV_ATTENTION_SLIDING_WINDOW, hparams.n_swa, false); + ml.get_key(LLM_KV_ATTENTION_LAYERNORM_RMS_EPS, hparams.f_norm_rms_eps); + + switch (hparams.n_layer) { +@@ -10762,7 +10762,7 @@ struct llm_build_context { + struct ggml_tensor * inp_pos = build_inp_pos(); + + // KQ_mask (mask for 1 head, it will be broadcasted to all heads) +- struct ggml_tensor * KQ_mask_swa = build_inp_KQ_mask_swa(); ++ struct ggml_tensor * KQ_mask = hparams.n_swa > 0 ? build_inp_KQ_mask_swa() : build_inp_KQ_mask(); + + for (int il = 0; il < n_layer; ++il) { + auto residual = inpL; +@@ -10820,7 +10820,7 @@ struct llm_build_context { + + cur = llm_build_kv(ctx0, lctx, kv_self, gf, + model.layers[il].wo, model.layers[il].bo, +- Kcur, Vcur, Qcur, KQ_mask_swa, n_tokens, kv_head, n_kv, 1.0f, cb, il); ++ Kcur, Vcur, Qcur, KQ_mask, n_tokens, kv_head, n_kv, 1.0f, cb, il); + } + + if (il == n_layer - 1) { +-- +2.45.2 + From 5e9db9fb0bcefbe599734b02dd030f4a347ce576 Mon Sep 17 00:00:00 2001 From: Michael Yang Date: Fri, 31 May 2024 20:00:49 -0700 Subject: [PATCH 180/384] refactor convert --- convert/convert.go | 243 +++++------- convert/convert_gemma.go | 103 ++++++ convert/convert_llama.go | 182 +++++++++ convert/convert_mixtral.go | 89 +++++ convert/convert_test.go | 25 +- convert/gemma.go | 102 ----- convert/llama.go | 159 -------- convert/mistral.go | 84 ----- convert/mixtral.go | 87 ----- convert/reader.go | 74 ++++ convert/reader_safetensors.go | 140 +++++++ convert/reader_torch.go | 46 +++ convert/safetensors.go | 309 ---------------- .../testdata/Mistral-7B-Instruct-v0.2.json | 2 +- .../testdata/Mixtral-8x7B-Instruct-v0.1.json | 349 +++++++++++++++++- convert/tokenizer.go | 265 ++++++++++--- convert/tokenizer_spm.go | 83 +++++ convert/torch.go | 287 -------------- llm/gguf.go | 326 +++++++--------- llm/memory_test.go | 6 +- server/model.go | 26 +- server/routes_create_test.go | 5 +- server/routes_generate_test.go | 8 +- server/sched_test.go | 8 +- 24 files changed, 1514 insertions(+), 1494 deletions(-) create mode 100644 convert/convert_gemma.go create mode 100644 convert/convert_llama.go create mode 100644 convert/convert_mixtral.go delete mode 100644 convert/gemma.go delete mode 100644 convert/llama.go delete mode 100644 convert/mistral.go delete mode 100644 convert/mixtral.go create mode 100644 convert/reader.go create mode 100644 convert/reader_safetensors.go create mode 100644 convert/reader_torch.go delete mode 100644 convert/safetensors.go create mode 100644 convert/tokenizer_spm.go delete mode 100644 convert/torch.go diff --git a/convert/convert.go b/convert/convert.go index 103de457c..4ad64d721 100644 --- a/convert/convert.go +++ b/convert/convert.go @@ -1,200 +1,123 @@ package convert import ( - "cmp" - "encoding/binary" "encoding/json" + "errors" "fmt" "io" "log/slog" "os" "path/filepath" - "slices" - "strings" - "google.golang.org/protobuf/proto" - - "github.com/ollama/ollama/convert/sentencepiece" "github.com/ollama/ollama/llm" ) -const ( - _ int32 = iota - tokenTypeNormal - tokenTypeUnknown - tokenTypeControl - tokenTypeUserDefined - tokenTypeUnused - tokenTypeByte -) - -type Params struct { - Architectures []string `json:"architectures"` - VocabSize int `json:"vocab_size"` - HiddenSize int `json:"hidden_size"` // n_embd - HiddenLayers int `json:"num_hidden_layers"` // n_layer - ContextSize int `json:"max_position_embeddings"` - IntermediateSize int `json:"intermediate_size"` - AttentionHeads int `json:"num_attention_heads"` // n_head - KeyValHeads int `json:"num_key_value_heads"` - NormEPS float64 `json:"rms_norm_eps"` - BoSTokenID int `json:"bos_token_id"` - EoSTokenID int `json:"eos_token_id"` - HeadDimension int `json:"head_dim"` - PaddingTokenID int `json:"pad_token_id"` - RopeFrequencyBase float64 `json:"rope_theta"` - - Experts int `json:"num_local_experts"` - ExpertsUsed int `json:"num_experts_per_tok"` - - PreTokenizer string - - ByteOrder +type Parameters struct { + Architectures []string `json:"architectures"` + VocabSize uint32 `json:"vocab_size"` } -type ByteOrder interface { - binary.ByteOrder - binary.AppendByteOrder +func (Parameters) KV(t *Tokenizer) llm.KV { + kv := llm.KV{ + "general.file_type": uint32(1), + "general.quantization_version": uint32(2), + "tokenizer.ggml.pre": t.Pre, + "tokenizer.ggml.model": t.Vocabulary.Model, + "tokenizer.ggml.tokens": t.Vocabulary.Tokens, + "tokenizer.ggml.scores": t.Vocabulary.Scores, + "tokenizer.ggml.token_type": t.Vocabulary.Types, + } + + if t.Template != "" { + kv["tokenizer.chat_template"] = t.Template + } + + for _, sv := range t.SpecialVocabulary { + kv[fmt.Sprintf("tokenizer.ggml.%s_token_id", sv.Key())] = uint32(sv.ID) + kv[fmt.Sprintf("tokenizer.ggml.add_%s_token", sv.Key())] = sv.AddToken + } + + return kv } -type ModelArch interface { - GetTensors() error - LoadVocab() error - WriteGGUF(io.WriteSeeker) error +func (Parameters) specialTypes() []string { + return []string{ + "bos", "eos", "unk", "sep", "pad", "cls", "mask", + } } -type ModelFormat interface { - GetLayerName(string) (string, error) - GetTensors(string, *Params) ([]llm.Tensor, error) - GetParams(string) (*Params, error) - GetModelArch(string, string, *Params) (ModelArch, error) +func (Parameters) writeFile(ws io.WriteSeeker, kv llm.KV, ts []*llm.Tensor) error { + return llm.WriteGGUF(ws, kv, ts) } -type ModelData struct { - Path string - Name string - Params *Params - Vocab *Vocab - Tensors []llm.Tensor - Format ModelFormat +type Converter interface { + // KV maps parameters to LLM key-values + KV(*Tokenizer) llm.KV + // Tensors maps input tensors to LLM tensors. Model specific modifications can be done here. + Tensors([]Tensor) []*llm.Tensor + + // tensorName returns the LLM tensor name for a specific input name + tensorName(string) string + // specialTypes returns any special token types the model uses + specialTypes() []string + writeFile(io.WriteSeeker, llm.KV, []*llm.Tensor) error } -func GetModelFormat(dirname string) (ModelFormat, error) { - files, err := filepath.Glob(filepath.Join(dirname, "*")) +func Convert(d string, ws io.WriteSeeker) error { + f, err := os.Open(filepath.Join(d, "config.json")) if err != nil { - return nil, err + return err + } + defer f.Close() + + var p Parameters + if err := json.NewDecoder(f).Decode(&p); err != nil { + return err } - for _, fn := range files { - if strings.HasSuffix(fn, ".safetensors") { - return &SafetensorFormat{}, nil - } else if strings.HasSuffix(fn, ".bin") || strings.HasSuffix(fn, ".pth") { - slog.Debug("model is torch") - return &TorchFormat{}, nil - } + if len(p.Architectures) < 1 { + return errors.New("unknown architecture") } - return nil, fmt.Errorf("couldn't determine model format") -} + var c Converter + switch p.Architectures[0] { + case "LlamaForCausalLM", "MistralForCausalLM": + c = &llama{} + case "MixtralForCausalLM": + c = &mixtral{} + case "GemmaForCausalLM": + c = &gemma{} + default: + return errors.New("unsupported architecture") + } -// Details on gguf's tokenizer can be found at: -// https://github.com/ggerganov/ggml/blob/master/docs/gguf.md#tokenizer -type Vocab struct { - Tokens []string - Scores []float32 - Types []int32 - Merges []string -} - -func LoadSentencePieceTokens(dirpath string, params *Params) (*Vocab, error) { - slog.Info(fmt.Sprintf("reading vocab from %s", filepath.Join(dirpath, "tokenizer.model"))) - in, err := os.ReadFile(filepath.Join(dirpath, "tokenizer.model")) + bts, err := os.ReadFile(filepath.Join(d, "config.json")) if err != nil { - return nil, err + return err } - // To regenerate sentencepiece from the protobufs use: - // protoc -I=./ --go_out=./ sentencepiece_model.proto - modelProto := &sentencepiece.ModelProto{} - if err := proto.Unmarshal(in, modelProto); err != nil { - return nil, err + if err := json.Unmarshal(bts, c); err != nil { + return err } - v := &Vocab{ - Tokens: make([]string, 0), - Scores: make([]float32, 0), - Types: make([]int32, 0), + t, err := parseTokenizer(d, c.specialTypes()) + if err != nil { + return err } - pieces := modelProto.GetPieces() - for _, p := range pieces { - v.Tokens = append(v.Tokens, p.GetPiece()) - v.Scores = append(v.Scores, p.GetScore()) - t := p.GetType() - switch t { - case sentencepiece.ModelProto_SentencePiece_UNKNOWN: - case sentencepiece.ModelProto_SentencePiece_CONTROL: - case sentencepiece.ModelProto_SentencePiece_UNUSED: - case sentencepiece.ModelProto_SentencePiece_BYTE: - default: - t = sentencepiece.ModelProto_SentencePiece_NORMAL - } - v.Types = append(v.Types, int32(t)) - } - - slog.Info(fmt.Sprintf("vocab size: %d", len(v.Tokens))) - - // add any additional tokens - addIn, err := os.ReadFile(filepath.Join(dirpath, "added_tokens.json")) - if os.IsNotExist(err) { - return v, nil - } else if err != nil { - return nil, err - } - - slog.Info("reading user defined tokens") - - var extraTokenData map[string]int - if err := json.Unmarshal(addIn, &extraTokenData); err != nil { - return nil, err - } - - type token struct { - key string - pos int - } - - extraTokens := make([]token, 0) - for k, id := range extraTokenData { - extraTokens = append(extraTokens, token{k, id}) - } - - slices.SortFunc(extraTokens, func(a, b token) int { - return cmp.Compare(a.pos, b.pos) - }) - - numToks := len(v.Tokens) - - for cnt, t := range extraTokens { - // the token id should match the specific index for the total number of tokens - if t.pos != cnt+numToks { - return nil, fmt.Errorf("token ID '%d' for '%s' doesn't match total token size", t.pos, t.key) - } - v.Tokens = append(v.Tokens, t.key) - v.Scores = append(v.Scores, -1000.0) - v.Types = append(v.Types, tokenTypeUserDefined) - } - slog.Info(fmt.Sprintf("vocab size w/ extra tokens: %d", len(v.Tokens))) - - if params.VocabSize > len(v.Tokens) { - missingTokens := params.VocabSize - len(v.Tokens) - slog.Warn(fmt.Sprintf("vocab is missing %d tokens", missingTokens)) - for cnt := range missingTokens { - v.Tokens = append(v.Tokens, fmt.Sprintf("", cnt+1)) - v.Scores = append(v.Scores, -1) - v.Types = append(v.Types, tokenTypeUserDefined) + if vocabSize := int(p.VocabSize); vocabSize > len(t.Vocabulary.Tokens) { + slog.Warn("vocabulary is smaller than expected, padding with dummy tokens", "expect", p.VocabSize, "actual", len(t.Vocabulary.Tokens)) + for i := range vocabSize - len(t.Vocabulary.Tokens) { + t.Vocabulary.Tokens = append(t.Vocabulary.Tokens, fmt.Sprintf("[PAD%d]", i)) + t.Vocabulary.Scores = append(t.Vocabulary.Scores, -1) + t.Vocabulary.Types = append(t.Vocabulary.Types, tokenTypeUserDefined) } } - return v, nil + ts, err := parseTensors(d) + if err != nil { + return err + } + + return c.writeFile(ws, c.KV(t), c.Tensors(ts)) } diff --git a/convert/convert_gemma.go b/convert/convert_gemma.go new file mode 100644 index 000000000..332fee7f2 --- /dev/null +++ b/convert/convert_gemma.go @@ -0,0 +1,103 @@ +package convert + +import ( + "strings" + + "github.com/pdevine/tensor" + "github.com/pdevine/tensor/native" + + "github.com/ollama/ollama/llm" +) + +type gemma struct { + Parameters + MaxPositionEmbeddings uint32 `json:"max_position_embeddings"` + HiddenSize uint32 `json:"hidden_size"` + HiddenLayers uint32 `json:"num_hidden_layers"` + IntermediateSize uint32 `json:"intermediate_size"` + NumAttentionHeads uint32 `json:"num_attention_heads"` + NumKeyValueHeads uint32 `json:"num_key_value_heads"` + RMSNormEPS float32 `json:"rms_norm_eps"` + HeadDim uint32 `json:"head_dim"` +} + +var _ Converter = (*gemma)(nil) + +func (p *gemma) KV(t *Tokenizer) llm.KV { + kv := p.Parameters.KV(t) + kv["general.architecture"] = "gemma" + kv["general.name"] = "gemma" + kv["gemma.context_length"] = p.MaxPositionEmbeddings + kv["gemma.embedding_length"] = p.HiddenSize + kv["gemma.block_count"] = p.HiddenLayers + kv["gemma.feed_forward_length"] = p.IntermediateSize + kv["gemma.attention.head_count"] = p.NumAttentionHeads + kv["gemma.attention.head_count_kv"] = p.NumKeyValueHeads + kv["gemma.attention.layer_norm_rms_epsilon"] = p.RMSNormEPS + kv["gemma.attention.key_length"] = p.HeadDim + kv["gemma.attention.value_length"] = p.HeadDim + kv["tokenizer.ggml.eot_token_id"] = uint32(107) + kv["tokenizer.ggml.middle_token_id"] = uint32(68) + kv["tokenizer.ggml.prefix_token_id"] = uint32(67) + kv["tokenizer.ggml.suffix_token_id"] = uint32(69) + return kv +} + +func (p *gemma) Tensors(ts []Tensor) []*llm.Tensor { + var out []*llm.Tensor + for _, t := range ts { + name := p.tensorName(t.Name()) + if strings.HasSuffix(name, "_norm.weight") { + t.SetRepacker(p.addOne) + } + + out = append(out, &llm.Tensor{ + Name: name, + Kind: t.Kind(), + Shape: t.Shape(), + WriterTo: t, + }) + } + + return out +} + +func (p *gemma) tensorName(n string) string { + return strings.NewReplacer( + "model.embed_tokens", "token_embd", + "model.norm", "output_norm", + "model.layers", "blk", + "input_layernorm", "attn_norm", + "self_attn.q_proj", "attn_q", + "self_attn.k_proj", "attn_k", + "self_attn.v_proj", "attn_v", + "self_attn.o_proj", "attn_output", + "mlp.gate_proj", "ffn_gate", + "mlp.down_proj", "ffn_down", + "mlp.up_proj", "ffn_up", + "post_attention_layernorm", "ffn_norm", + "block_sparse_moe.gate", "ffn_inp", + ).Replace(n) +} + +func (*gemma) addOne(_ string, data []float32, shape []uint64) ([]float32, error) { + n := tensor.New(tensor.WithShape(int(shape[0])), tensor.WithBacking(data)) + ones := tensor.Ones(tensor.Float32, int(shape[0])) + + n, err := n.Add(ones) + if err != nil { + return nil, err + } + + ts, err := native.SelectF32(n, 0) + if err != nil { + return nil, err + } + + var f32s []float32 + for _, t := range ts { + f32s = append(f32s, t...) + } + + return f32s, nil +} diff --git a/convert/convert_llama.go b/convert/convert_llama.go new file mode 100644 index 000000000..700049d32 --- /dev/null +++ b/convert/convert_llama.go @@ -0,0 +1,182 @@ +package convert + +import ( + "cmp" + "fmt" + "strings" + + "github.com/ollama/ollama/llm" + "github.com/pdevine/tensor" + "github.com/pdevine/tensor/native" +) + +type llama struct { + Parameters + NLayers uint32 `json:"n_layers"` + NumHiddenLayers uint32 `json:"num_hidden_layers"` + NLayer uint32 `json:"n_layer"` + MaxPositionEmbeddings uint32 `json:"max_position_embeddings"` + NCtx uint32 `json:"n_ctx"` + HiddenSize uint32 `json:"hidden_size"` + NEmbd uint32 `json:"n_embd"` + IntermediateSize uint32 `json:"intermediate_size"` + NInner uint32 `json:"n_inner"` + NumAttentionHeads uint32 `json:"num_attention_heads"` + NHead uint32 `json:"n_head"` + NumKeyValueHeads uint32 `json:"num_key_value_heads"` + RopeTheta float32 `json:"rope_theta"` + RopeScaling struct { + Type string `json:"type"` + Factor float32 `json:"factor"` + } `json:"rope_scaling"` + RMSNormEPS float32 `json:"rms_norm_eps"` + LayerNormEPS float32 `json:"layer_norm_eps"` + LayerNormEpsilon float32 `json:"layer_norm_epsilon"` + NormEpsilon float32 `json:"norm_epsilon"` + HeadDim uint32 `json:"head_dim"` +} + +var _ Converter = (*llama)(nil) + +func (p *llama) KV(t *Tokenizer) llm.KV { + kv := p.Parameters.KV(t) + kv["general.architecture"] = "llama" + kv["general.name"] = "llama" + kv["llama.vocab_size"] = p.VocabSize + + kv["llama.block_count"] = cmp.Or(p.NLayers, p.NumHiddenLayers, p.NLayer) + + if contextLength := cmp.Or(p.MaxPositionEmbeddings, p.NCtx); contextLength > 0 { + kv["llama.context_length"] = contextLength + } + + if embeddingLength := cmp.Or(p.HiddenSize, p.NEmbd); embeddingLength > 0 { + kv["llama.embedding_length"] = cmp.Or(p.HiddenSize, p.NEmbd) + } + + if feedForwardLength := cmp.Or(p.IntermediateSize, p.NInner); feedForwardLength > 0 { + kv["llama.feed_forward_length"] = cmp.Or(p.IntermediateSize, p.NInner) + } + + if headCount := cmp.Or(p.NumAttentionHeads, p.NHead); headCount > 0 { + kv["llama.attention.head_count"] = cmp.Or(p.NumAttentionHeads, p.NHead) + kv["llama.rope.dimension_count"] = p.HiddenSize / headCount + } + + if p.RopeTheta > 0 { + kv["llama.rope.freq_base"] = p.RopeTheta + } + + if p.RopeScaling.Type == "linear" { + kv["llama.rope.scaling.type"] = p.RopeScaling.Type + kv["llama.rope.scaling.factor"] = p.RopeScaling.Factor + } + + if p.NumKeyValueHeads > 0 { + kv["llama.attention.head_count_kv"] = p.NumKeyValueHeads + } + + if p.RMSNormEPS > 0 { + kv["llama.attention.layer_norm_rms_epsilon"] = p.RMSNormEPS + } + + if layerNormEpsilon := cmp.Or(p.LayerNormEPS, p.LayerNormEpsilon, p.NormEpsilon); layerNormEpsilon > 0 { + kv["llama.attention.layer_norm_epsilon"] = layerNormEpsilon + } + + if p.HeadDim > 0 { + kv["llama.attention.key_length"] = p.HeadDim + kv["llama.attention.value_length"] = p.HeadDim + } + + if len(t.Merges) > 0 { + kv["tokenizer.ggml.merges"] = t.Merges + } + + return kv +} + +func (p *llama) Tensors(ts []Tensor) []*llm.Tensor { + var out []*llm.Tensor + for _, t := range ts { + name := p.tensorName(t.Name()) + if strings.HasSuffix(name, "attn_q.weight") || + strings.HasSuffix(name, "attn_k.weight") { + t.SetRepacker(p.repack) + } + + out = append(out, &llm.Tensor{ + Name: name, + Kind: t.Kind(), + Shape: t.Shape(), + WriterTo: t, + }) + } + + return out +} + +func (p *llama) tensorName(n string) string { + return strings.NewReplacer( + "lm_head", "output", + "model.embed_tokens", "token_embd", + "model.norm", "output_norm", + "model.layers", "blk", + "input_layernorm", "attn_norm", + "self_attn.q_proj", "attn_q", + "self_attn.k_proj", "attn_k", + "self_attn.v_proj", "attn_v", + "self_attn.o_proj", "attn_output", + "mlp.gate_proj", "ffn_gate", + "mlp.down_proj", "ffn_down", + "mlp.up_proj", "ffn_up", + "post_attention_layernorm", "ffn_norm", + // mixtral + "block_sparse_moe.gate", "ffn_gate_inp", + ).Replace(n) +} + +func (p *llama) repack(name string, data []float32, shape []uint64) ([]float32, error) { + var dims []int + for _, dim := range shape { + dims = append(dims, int(dim)) + } + + var heads uint32 + if strings.HasSuffix(name, "q_proj.weight") { + heads = p.NumAttentionHeads + } else if strings.HasSuffix(name, "k_proj.weight") { + heads = cmp.Or(p.NumKeyValueHeads, p.NumAttentionHeads) + } else { + return nil, fmt.Errorf("unknown tensor for repack: %s", name) + } + + n := tensor.New(tensor.WithShape(dims...), tensor.WithBacking(data)) + if err := n.Reshape(append([]int{int(heads), 2, dims[0] / int(heads) / 2}, dims[1:]...)...); err != nil { + return nil, err + } + + if err := n.T(0, 2, 1, 3); err != nil { + return nil, err + } + + if err := n.Reshape(dims...); err != nil { + return nil, err + } + + if err := n.Transpose(); err != nil { + return nil, err + } + + ts, err := native.SelectF32(n, 1) + if err != nil { + return nil, err + } + + var f32s []float32 + for _, t := range ts { + f32s = append(f32s, t...) + } + + return f32s, nil +} diff --git a/convert/convert_mixtral.go b/convert/convert_mixtral.go new file mode 100644 index 000000000..c55a27f81 --- /dev/null +++ b/convert/convert_mixtral.go @@ -0,0 +1,89 @@ +package convert + +import ( + "fmt" + "io" + "slices" + "strings" + + "github.com/ollama/ollama/llm" +) + +type mixtral struct { + llama + NumLocalExperts uint32 `json:"num_local_experts"` + NumExpertsPerToken uint32 `json:"num_experts_per_tok"` +} + +var _ Converter = (*mixtral)(nil) + +func (p *mixtral) KV(t *Tokenizer) llm.KV { + kv := p.llama.KV(t) + + if p.NumLocalExperts > 0 { + kv["llama.expert_count"] = p.NumLocalExperts + } + + if p.NumExpertsPerToken > 0 { + kv["llama.expert_used_count"] = p.NumExpertsPerToken + } + + return kv +} + +func (p *mixtral) Tensors(ts []Tensor) []*llm.Tensor { + oldnew := []string{ + "model.layers", "blk", + "w1", "ffn_gate_exps", + "w2", "ffn_down_exps", + "w3", "ffn_up_exps", + } + + for i := range p.NumLocalExperts { + oldnew = append(oldnew, fmt.Sprintf(".block_sparse_moe.experts.%d.", i), ".") + } + + // group experts of the same layer (model.layers.%d) and type (w[123]) into a single tensor + namer := strings.NewReplacer(oldnew...) + experts := make(map[string]experts) + + // merge experts into a single tensor while removing them from ts + ts = slices.DeleteFunc(ts, func(t Tensor) bool { + if !strings.Contains(t.Name(), ".block_sparse_moe.experts.") { + return false + } + + name := namer.Replace(t.Name()) + experts[name] = append(experts[name], t) + return true + }) + + var out []*llm.Tensor + for n, e := range experts { + // TODO(mxyng): sanity check experts + out = append(out, &llm.Tensor{ + Name: n, + Kind: e[0].Kind(), + Shape: append([]uint64{uint64(len(e))}, e[0].Shape()...), + WriterTo: e, + }) + } + + return append(out, p.llama.Tensors(ts)...) +} + +type experts []Tensor + +func (e experts) WriteTo(w io.Writer) (int64, error) { + // TODO(mxyng): experts _should_ be numerically sorted by expert but this should check + for _, t := range e { + // the canonical merged experts tensor stacks all experts along a new, 0 axis, + // e.g. `tensor.Stack(0, e[0], e[1:]...)`, which requires allocating temporary buffers + // this accomplishes the same thing by writing each expert tensor in sequence + if _, err := t.WriteTo(w); err != nil { + return 0, err + } + } + + return 0, nil +} diff --git a/convert/convert_test.go b/convert/convert_test.go index a3727bedc..0fbd436f5 100644 --- a/convert/convert_test.go +++ b/convert/convert_test.go @@ -20,36 +20,13 @@ import ( func convertFull(t *testing.T, d string) (*os.File, llm.KV, llm.Tensors) { t.Helper() - mf, err := GetModelFormat(d) - if err != nil { - t.Fatal(err) - } - - params, err := mf.GetParams(d) - if err != nil { - t.Fatal(err) - } - - arch, err := mf.GetModelArch("", d, params) - if err != nil { - t.Fatal(err) - } - - if err := arch.LoadVocab(); err != nil { - t.Fatal(err) - } - - if err := arch.GetTensors(); err != nil { - t.Fatal(err) - } - f, err := os.CreateTemp(t.TempDir(), "f16") if err != nil { t.Fatal(err) } defer f.Close() - if err := arch.WriteGGUF(f); err != nil { + if err := Convert(d, f); err != nil { t.Fatal(err) } diff --git a/convert/gemma.go b/convert/gemma.go deleted file mode 100644 index d01ffedf1..000000000 --- a/convert/gemma.go +++ /dev/null @@ -1,102 +0,0 @@ -package convert - -import ( - "fmt" - "io" - "log/slog" - "strings" - - "github.com/pdevine/tensor" - "github.com/pdevine/tensor/native" - - "github.com/ollama/ollama/llm" -) - -type GemmaModel struct { - ModelData -} - -func addOnes(data []float32, vectorSize int) ([]float32, error) { - n := tensor.New(tensor.WithShape(vectorSize), tensor.WithBacking(data)) - ones := tensor.Ones(tensor.Float32, vectorSize) - - n, err := n.Add(ones) - if err != nil { - return nil, err - } - - ts, err := native.SelectF32(n, 0) - if err != nil { - return nil, err - } - - var f32s []float32 - for _, t := range ts { - f32s = append(f32s, t...) - } - - return f32s, nil -} - -func (m *GemmaModel) GetTensors() error { - t, err := m.Format.GetTensors(m.Path, m.Params) - if err != nil { - return err - } - - slog.Debug(fmt.Sprintf("Total tensors: %d", len(t))) - for _, l := range t { - if strings.HasSuffix(l.Name, "norm.weight") { - wt := l.WriterTo.(safetensorWriterTo) - wt.repacker = m.Repack - l.WriterTo = wt - } - m.Tensors = append(m.Tensors, l) - } - - return nil -} - -func (m *GemmaModel) LoadVocab() error { - v, err := LoadSentencePieceTokens(m.Path, m.Params) - if err != nil { - return err - } - m.Vocab = v - return nil -} - -func (m *GemmaModel) Repack(_ string, data []float32, shape []uint64) ([]float32, error) { - return addOnes(data, int(shape[0])) -} - -func (m *GemmaModel) WriteGGUF(ws io.WriteSeeker) error { - kv := llm.KV{ - "general.architecture": "gemma", - "general.name": m.Name, - "gemma.context_length": uint32(m.Params.ContextSize), - "gemma.embedding_length": uint32(m.Params.HiddenSize), - "gemma.block_count": uint32(m.Params.HiddenLayers), - "gemma.feed_forward_length": uint32(m.Params.IntermediateSize), - "gemma.attention.head_count": uint32(m.Params.AttentionHeads), - "gemma.attention.head_count_kv": uint32(m.Params.KeyValHeads), - "gemma.attention.layer_norm_rms_epsilon": float32(m.Params.NormEPS), - "gemma.attention.key_length": uint32(m.Params.HeadDimension), - "gemma.attention.value_length": uint32(m.Params.HeadDimension), - "general.file_type": uint32(1), - "tokenizer.ggml.model": "llama", - - "tokenizer.ggml.tokens": m.Vocab.Tokens, - "tokenizer.ggml.scores": m.Vocab.Scores, - "tokenizer.ggml.token_type": m.Vocab.Types, - - "tokenizer.ggml.bos_token_id": uint32(m.Params.BoSTokenID), - "tokenizer.ggml.eos_token_id": uint32(m.Params.EoSTokenID), - "tokenizer.ggml.padding_token_id": uint32(m.Params.PaddingTokenID), - "tokenizer.ggml.unknown_token_id": uint32(3), - "tokenizer.ggml.add_bos_token": true, - "tokenizer.ggml.add_eos_token": false, - } - - return llm.NewGGUFV3(m.Params.ByteOrder).Encode(ws, kv, m.Tensors) -} diff --git a/convert/llama.go b/convert/llama.go deleted file mode 100644 index b4211b02d..000000000 --- a/convert/llama.go +++ /dev/null @@ -1,159 +0,0 @@ -package convert - -import ( - "cmp" - "errors" - "fmt" - "io" - "os" - "path/filepath" - "regexp" - "strings" - - "github.com/pdevine/tensor" - "github.com/pdevine/tensor/native" - - "github.com/ollama/ollama/llm" -) - -type LlamaModel struct { - ModelData -} - -func (m *LlamaModel) GetTensors() error { - t, err := m.Format.GetTensors(m.Path, m.Params) - if err != nil { - return err - } - - pattern := `^blk\.[0-9]+\.attn_(?Pq|k)\.weight$` - re, err := regexp.Compile(pattern) - if err != nil { - return err - } - - for _, l := range t { - matches := re.FindAllStringSubmatch(l.Name, -1) - if len(matches) > 0 { - switch m.Format.(type) { - case *TorchFormat: - wt := l.WriterTo.(torchWriterTo) - wt.repacker = m.Repack - l.WriterTo = wt - case *SafetensorFormat: - wt := l.WriterTo.(safetensorWriterTo) - wt.repacker = m.Repack - l.WriterTo = wt - } - } - m.Tensors = append(m.Tensors, l) - } - - return nil -} - -func (m *LlamaModel) LoadVocab() (err error) { - pre, ts, merges, err := parseTokens(filepath.Join(m.Path, "tokenizer.json")) - if errors.Is(err, os.ErrNotExist) { - return nil - } else if err != nil { - return err - } - - m.Vocab = &Vocab{} - for _, t := range ts { - m.Vocab.Tokens = append(m.Vocab.Tokens, t.Content) - m.Vocab.Types = append(m.Vocab.Types, t.Type()) - } - - m.Vocab.Merges = merges - m.Params.PreTokenizer = pre - return nil -} - -func (m *LlamaModel) WriteGGUF(ws io.WriteSeeker) error { - kv := llm.KV{ - "general.architecture": "llama", - "general.name": m.Name, - "llama.vocab_size": uint32(len(m.Vocab.Tokens)), - "llama.context_length": uint32(m.Params.ContextSize), - "llama.embedding_length": uint32(m.Params.HiddenSize), - "llama.block_count": uint32(m.Params.HiddenLayers), - "llama.feed_forward_length": uint32(m.Params.IntermediateSize), - "llama.rope.freq_base": float32(m.Params.RopeFrequencyBase), - "llama.rope.dimension_count": uint32(m.Params.HiddenSize / m.Params.AttentionHeads), - "llama.attention.head_count": uint32(m.Params.AttentionHeads), - "llama.attention.head_count_kv": uint32(m.Params.KeyValHeads), - "llama.attention.layer_norm_rms_epsilon": float32(m.Params.NormEPS), - "general.file_type": uint32(1), - "tokenizer.ggml.model": "gpt2", - - "tokenizer.ggml.pre": m.Params.PreTokenizer, - "tokenizer.ggml.tokens": m.Vocab.Tokens, - "tokenizer.ggml.token_type": m.Vocab.Types, - - "tokenizer.ggml.bos_token_id": uint32(m.Params.BoSTokenID), - "tokenizer.ggml.eos_token_id": uint32(m.Params.EoSTokenID), - "tokenizer.ggml.unknown_token_id": uint32(0), - } - - if len(m.Vocab.Merges) > 0 { - kv["tokenizer.ggml.merges"] = m.Vocab.Merges - } else { - kv["tokenizer.ggml.scores"] = m.Vocab.Scores - } - - return llm.NewGGUFV3(m.Params.ByteOrder).Encode(ws, kv, m.Tensors) -} - -func (m *LlamaModel) Repack(name string, data []float32, shape []uint64) ([]float32, error) { - return llamaRepack(name, m.Params, data, shape) -} - -func llamaRepack(name string, params *Params, data []float32, shape []uint64) ([]float32, error) { - var dims []int - for _, dim := range shape { - if dim != 0 { - dims = append(dims, int(dim)) - } - } - - var heads int - switch { - case strings.HasSuffix(name, "attn_q.weight"): - heads = params.AttentionHeads - case strings.HasSuffix(name, "attn_k.weight"): - heads = cmp.Or(params.KeyValHeads, params.AttentionHeads) - default: - return nil, fmt.Errorf("unknown tensor name: %s", name) - } - - n := tensor.New(tensor.WithShape(dims...), tensor.WithBacking(data)) - if err := n.Reshape(append([]int{heads, 2, dims[0] / heads / 2}, dims[1:]...)...); err != nil { - return nil, err - } - - if err := n.T(0, 2, 1, 3); err != nil { - return nil, err - } - - if err := n.Reshape(dims...); err != nil { - return nil, err - } - - if err := n.Transpose(); err != nil { - return nil, err - } - - ts, err := native.SelectF32(n, 1) - if err != nil { - return nil, err - } - - var f32s []float32 - for _, t := range ts { - f32s = append(f32s, t...) - } - - return f32s, nil -} diff --git a/convert/mistral.go b/convert/mistral.go deleted file mode 100644 index 8fe066d6a..000000000 --- a/convert/mistral.go +++ /dev/null @@ -1,84 +0,0 @@ -package convert - -import ( - "io" - "regexp" - - "github.com/ollama/ollama/llm" -) - -type MistralModel struct { - ModelData -} - -func (m *MistralModel) GetTensors() error { - t, err := m.Format.GetTensors(m.Path, m.Params) - if err != nil { - return err - } - - pattern := `^blk\.[0-9]+\.attn_(?Pq|k)\.weight$` - re, err := regexp.Compile(pattern) - if err != nil { - return err - } - - for _, l := range t { - matches := re.FindAllStringSubmatch(l.Name, -1) - if len(matches) > 0 { - wt := l.WriterTo.(safetensorWriterTo) - wt.repacker = m.Repack - l.WriterTo = wt - } - m.Tensors = append(m.Tensors, l) - } - - return nil -} - -func (m *MistralModel) LoadVocab() error { - v, err := LoadSentencePieceTokens(m.Path, m.Params) - if err != nil { - return err - } - m.Vocab = v - return nil -} - -func (m *MistralModel) WriteGGUF(ws io.WriteSeeker) error { - kv := llm.KV{ - "general.architecture": "llama", - "general.name": m.Name, - "llama.context_length": uint32(m.Params.ContextSize), - "llama.embedding_length": uint32(m.Params.HiddenSize), - "llama.block_count": uint32(m.Params.HiddenLayers), - "llama.feed_forward_length": uint32(m.Params.IntermediateSize), - "llama.rope.dimension_count": uint32(m.Params.HiddenSize / m.Params.AttentionHeads), - "llama.attention.head_count": uint32(m.Params.AttentionHeads), - "llama.attention.head_count_kv": uint32(m.Params.KeyValHeads), - "llama.attention.layer_norm_rms_epsilon": float32(m.Params.NormEPS), - "general.file_type": uint32(1), - "tokenizer.ggml.model": "llama", - - "tokenizer.ggml.tokens": m.Vocab.Tokens, - "tokenizer.ggml.scores": m.Vocab.Scores, - "tokenizer.ggml.token_type": m.Vocab.Types, - - "tokenizer.ggml.bos_token_id": uint32(m.Params.BoSTokenID), - "tokenizer.ggml.eos_token_id": uint32(m.Params.EoSTokenID), - "tokenizer.ggml.add_bos_token": true, - "tokenizer.ggml.add_eos_token": false, - "tokenizer.ggml.unknown_token_id": uint32(0), - } - - if m.Params.HeadDimension > 0 { - kv["llama.attention.key_length"] = uint32(m.Params.HeadDimension) - kv["llama.attention.value_length"] = uint32(m.Params.HeadDimension) - } - - return llm.NewGGUFV3(m.Params.ByteOrder).Encode(ws, kv, m.Tensors) -} - -func (m *MistralModel) Repack(name string, data []float32, shape []uint64) ([]float32, error) { - return llamaRepack(name, m.Params, data, shape) -} diff --git a/convert/mixtral.go b/convert/mixtral.go deleted file mode 100644 index baea68cd3..000000000 --- a/convert/mixtral.go +++ /dev/null @@ -1,87 +0,0 @@ -package convert - -import ( - "io" - "regexp" - - "github.com/ollama/ollama/llm" -) - -type MixtralModel struct { - ModelData -} - -func (m *MixtralModel) GetTensors() error { - t, err := m.Format.GetTensors(m.Path, m.Params) - if err != nil { - return err - } - - pattern := `^blk\.[0-9]+\.attn_(?Pq|k)\.weight$` - re, err := regexp.Compile(pattern) - if err != nil { - return err - } - - for _, l := range t { - matches := re.FindAllStringSubmatch(l.Name, -1) - if len(matches) > 0 { - wt := l.WriterTo.(safetensorWriterTo) - wt.repacker = m.Repack - l.WriterTo = wt - } - m.Tensors = append(m.Tensors, l) - } - - return nil -} - -func (m *MixtralModel) LoadVocab() error { - v, err := LoadSentencePieceTokens(m.Path, m.Params) - if err != nil { - return err - } - m.Vocab = v - return nil -} - -func (m *MixtralModel) WriteGGUF(ws io.WriteSeeker) error { - kv := llm.KV{ - "general.architecture": "llama", - "general.name": m.Name, - "llama.block_count": uint32(m.Params.HiddenLayers), - "llama.context_length": uint32(m.Params.ContextSize), - "llama.embedding_length": uint32(m.Params.HiddenSize), - "llama.feed_forward_length": uint32(m.Params.IntermediateSize), - "llama.attention.head_count": uint32(m.Params.AttentionHeads), - "llama.attention.head_count_kv": uint32(m.Params.KeyValHeads), - - "llama.rope.freq_base": float32(m.Params.RopeFrequencyBase), - "llama.attention.layer_norm_rms_epsilon": float32(m.Params.NormEPS), - - "llama.expert_count": uint32(m.Params.Experts), - "llama.expert_used_count": uint32(m.Params.ExpertsUsed), - - "llama.vocab_size": uint32(len(m.Vocab.Tokens)), - "llama.rope.dimension_count": uint32(m.Params.HiddenSize / m.Params.AttentionHeads), - - "general.file_type": uint32(1), - "tokenizer.ggml.model": "llama", - - "tokenizer.ggml.tokens": m.Vocab.Tokens, - "tokenizer.ggml.scores": m.Vocab.Scores, - "tokenizer.ggml.token_type": m.Vocab.Types, - - "tokenizer.ggml.bos_token_id": uint32(m.Params.BoSTokenID), - "tokenizer.ggml.eos_token_id": uint32(m.Params.EoSTokenID), - "tokenizer.ggml.unknown_token_id": uint32(0), - "tokenizer.ggml.add_bos_token": true, - "tokenizer.ggml.add_eos_token": false, - } - - return llm.NewGGUFV3(m.Params.ByteOrder).Encode(ws, kv, m.Tensors) -} - -func (m *MixtralModel) Repack(name string, data []float32, shape []uint64) ([]float32, error) { - return llamaRepack(name, m.Params, data, shape) -} diff --git a/convert/reader.go b/convert/reader.go new file mode 100644 index 000000000..9be8ac2eb --- /dev/null +++ b/convert/reader.go @@ -0,0 +1,74 @@ +package convert + +import ( + "errors" + "io" + "path/filepath" + "strings" +) + +type Tensor interface { + Name() string + Shape() []uint64 + Kind() uint32 + SetRepacker(repacker) + WriteTo(io.Writer) (int64, error) +} + +type tensorBase struct { + name string + shape []uint64 + repacker +} + +func (t tensorBase) Name() string { + return t.name +} + +func (t tensorBase) Shape() []uint64 { + return t.shape +} + +func (t tensorBase) Kind() uint32 { + if strings.HasSuffix(t.name, ".block_sparse_moe.gate.weight") { + return 0 + } + + switch len(t.shape) { + case 0: + panic("invalid tensor shape") + case 1: + return 0 + default: + return 1 + } +} + +func (t *tensorBase) SetRepacker(fn repacker) { + t.repacker = fn +} + +type repacker func(string, []float32, []uint64) ([]float32, error) + +func parseTensors(d string) ([]Tensor, error) { + patterns := map[string]func(...string) ([]Tensor, error){ + "model-*-of-*.safetensors": parseSafetensors, + "model.safetensors": parseSafetensors, + "pytorch_model-*-of-*.bin": parseTorch, + "pytorch_model.bin": parseTorch, + "consolidated.*.pth": parseTorch, + } + + for pattern, parseFn := range patterns { + matches, err := filepath.Glob(filepath.Join(d, pattern)) + if err != nil { + return nil, err + } + + if len(matches) > 0 { + return parseFn(matches...) + } + } + + return nil, errors.New("unknown tensor format") +} diff --git a/convert/reader_safetensors.go b/convert/reader_safetensors.go new file mode 100644 index 000000000..440581af8 --- /dev/null +++ b/convert/reader_safetensors.go @@ -0,0 +1,140 @@ +package convert + +import ( + "bytes" + "encoding/binary" + "encoding/json" + "fmt" + "io" + "os" + "slices" + + "github.com/d4l3k/go-bfloat16" + "github.com/x448/float16" + "golang.org/x/exp/maps" +) + +type safetensorMetadata struct { + Type string `json:"dtype"` + Shape []uint64 `json:"shape"` + Offsets []int64 `json:"data_offsets"` +} + +func parseSafetensors(ps ...string) ([]Tensor, error) { + var ts []Tensor + for _, p := range ps { + f, err := os.Open(p) + if err != nil { + return nil, err + } + defer f.Close() + + var n int64 + if err := binary.Read(f, binary.LittleEndian, &n); err != nil { + return nil, err + } + + b := bytes.NewBuffer(make([]byte, 0, n)) + if _, err = io.CopyN(b, f, n); err != nil { + return nil, err + } + + var headers map[string]safetensorMetadata + if err := json.NewDecoder(b).Decode(&headers); err != nil { + return nil, err + } + + keys := maps.Keys(headers) + slices.Sort(keys) + + for _, key := range keys { + if value := headers[key]; value.Type != "" { + ts = append(ts, safetensor{ + path: p, + dtype: value.Type, + offset: safetensorsPad(n, value.Offsets[0]), + size: safetensorsPad(n, value.Offsets[1]) - safetensorsPad(n, value.Offsets[0]), + tensorBase: &tensorBase{ + name: key, + shape: value.Shape, + }, + }) + } + } + } + + return ts, nil +} + +func safetensorsPad(n, s int64) int64 { + return 8 + n + s +} + +type safetensor struct { + path string + dtype string + offset int64 + size int64 + *tensorBase +} + +func (st safetensor) WriteTo(w io.Writer) (int64, error) { + f, err := os.Open(st.path) + if err != nil { + return 0, err + } + defer f.Close() + + if _, err = f.Seek(st.offset, io.SeekStart); err != nil { + return 0, err + } + + var f32s []float32 + switch st.dtype { + case "F32": + f32s = make([]float32, st.size/4) + if err = binary.Read(f, binary.LittleEndian, f32s); err != nil { + return 0, err + } + case "F16": + u16s := make([]uint16, st.size/2) + if err = binary.Read(f, binary.LittleEndian, u16s); err != nil { + return 0, err + } + + for _, b := range u16s { + f32s = append(f32s, float16.Frombits(b).Float32()) + } + + case "BF16": + u8s := make([]uint8, st.size) + if err = binary.Read(f, binary.LittleEndian, u8s); err != nil { + return 0, err + } + + f32s = bfloat16.DecodeFloat32(u8s) + default: + return 0, fmt.Errorf("unknown data type: %s", st.dtype) + } + + if st.repacker != nil { + f32s, err = st.repacker(st.Name(), f32s, st.Shape()) + if err != nil { + return 0, err + } + } + + switch st.Kind() { + case 0: + return 0, binary.Write(w, binary.LittleEndian, f32s) + case 1: + f16s := make([]uint16, len(f32s)) + for i := range f32s { + f16s[i] = float16.Fromfloat32(f32s[i]).Bits() + } + + return 0, binary.Write(w, binary.LittleEndian, f16s) + default: + return 0, fmt.Errorf("unknown storage type: %d", st.Kind()) + } +} diff --git a/convert/reader_torch.go b/convert/reader_torch.go new file mode 100644 index 000000000..1428706e2 --- /dev/null +++ b/convert/reader_torch.go @@ -0,0 +1,46 @@ +package convert + +import ( + "io" + + "github.com/nlpodyssey/gopickle/pytorch" + "github.com/nlpodyssey/gopickle/types" +) + +func parseTorch(ps ...string) ([]Tensor, error) { + var ts []Tensor + for _, p := range ps { + pt, err := pytorch.Load(p) + if err != nil { + return nil, err + } + + for _, k := range pt.(*types.Dict).Keys() { + t := pt.(*types.Dict).MustGet(k) + + var shape []uint64 + for dim := range t.(*pytorch.Tensor).Size { + shape = append(shape, uint64(dim)) + } + + ts = append(ts, torch{ + storage: t.(*pytorch.Tensor).Source, + tensorBase: &tensorBase{ + name: k.(string), + shape: shape, + }, + }) + } + } + + return ts, nil +} + +type torch struct { + storage pytorch.StorageInterface + *tensorBase +} + +func (pt torch) WriteTo(w io.Writer) (int64, error) { + return 0, nil +} diff --git a/convert/safetensors.go b/convert/safetensors.go deleted file mode 100644 index f45687f10..000000000 --- a/convert/safetensors.go +++ /dev/null @@ -1,309 +0,0 @@ -package convert - -import ( - "bytes" - "encoding/binary" - "encoding/json" - "fmt" - "io" - "os" - "path/filepath" - "regexp" - "slices" - "strings" - - "github.com/d4l3k/go-bfloat16" - "github.com/x448/float16" - - "github.com/ollama/ollama/llm" -) - -type safetensorWriterTo struct { - t *llm.Tensor - - params *Params - bo ByteOrder - - filename string - dtype string - - offset, size int64 - repacker func(string, []float32, []uint64) ([]float32, error) -} - -type safetensorMetadata struct { - Type string `json:"dtype"` - Shape []uint64 `json:"shape"` - Offsets []int64 `json:"data_offsets"` -} - -type SafetensorFormat struct{} - -func (m *SafetensorFormat) GetTensors(dirpath string, params *Params) ([]llm.Tensor, error) { - var tensors []llm.Tensor - matches, err := filepath.Glob(filepath.Join(dirpath, "*.safetensors")) - if err != nil { - return nil, err - } - - var offset uint64 - for _, f := range matches { - var t []llm.Tensor - var err error - t, offset, err = m.readTensors(f, offset, params) - if err != nil { - return nil, err - } - - tensors = append(tensors, t...) - } - return tensors, nil -} - -func (m *SafetensorFormat) readTensors(fn string, offset uint64, params *Params) ([]llm.Tensor, uint64, error) { - f, err := os.Open(fn) - if err != nil { - return nil, 0, err - } - defer f.Close() - - var n int64 - if err := binary.Read(f, binary.LittleEndian, &n); err != nil { - return nil, 0, err - } - - b := bytes.NewBuffer(make([]byte, 0, n)) - if _, err = io.CopyN(b, f, n); err != nil { - return nil, 0, err - } - - var headers map[string]safetensorMetadata - if err := json.NewDecoder(b).Decode(&headers); err != nil { - return nil, 0, err - } - - var keys []string - for key := range headers { - if !strings.HasSuffix(key, "self_attn.rotary_embd.inv_freq") { - keys = append(keys, key) - } - } - - slices.Sort(keys) - - var tensors []llm.Tensor - for _, key := range keys { - value := headers[key] - - var kind uint32 - switch len(value.Shape) { - case 0: - // valuedata - continue - case 2: - kind = 1 - } - - name, err := m.GetLayerName(key) - if err != nil { - return nil, 0, err - } - - shape := make([]uint64, len(value.Shape)) - copy(shape, value.Shape) - - pad := func(s int64) int64 { - return 8 + n + s - } - - t := llm.Tensor{ - Name: name, - Kind: kind, - Offset: offset, - Shape: shape, - } - - t.WriterTo = safetensorWriterTo{ - t: &t, - params: params, - bo: params.ByteOrder, - filename: fn, - dtype: value.Type, - offset: pad(value.Offsets[0]), - size: pad(value.Offsets[1]) - pad(value.Offsets[0]), - } - - offset += t.Size() - tensors = append(tensors, t) - } - - return tensors, offset, nil -} - -func (m *SafetensorFormat) GetParams(dirpath string) (*Params, error) { - f, err := os.Open(filepath.Join(dirpath, "config.json")) - if err != nil { - return nil, err - } - defer f.Close() - - var params Params - - if err := json.NewDecoder(f).Decode(¶ms); err != nil { - return nil, err - } - - params.ByteOrder = binary.LittleEndian - return ¶ms, nil -} - -func (m *SafetensorFormat) GetLayerName(n string) (string, error) { - directMap := map[string]string{ - "model.embed_tokens.weight": "token_embd.weight", - "lm_head.weight": "output.weight", - "model.norm.weight": "output_norm.weight", - } - - tMap := map[string]string{ - "model.layers.(\\d+).input_layernorm.weight": "blk.$1.attn_norm.weight", - "model.layers.(\\d+).mlp.down_proj.weight": "blk.$1.ffn_down.weight", - "model.layers.(\\d+).mlp.gate_proj.weight": "blk.$1.ffn_gate.weight", - "model.layers.(\\d+).mlp.up_proj.weight": "blk.$1.ffn_up.weight", - "model.layers.(\\d+).post_attention_layernorm.weight": "blk.$1.ffn_norm.weight", - "model.layers.(\\d+).self_attn.k_proj.weight": "blk.$1.attn_k.weight", - "model.layers.(\\d+).self_attn.o_proj.weight": "blk.$1.attn_output.weight", - "model.layers.(\\d+).self_attn.q_proj.weight": "blk.$1.attn_q.weight", - "model.layers.(\\d+).self_attn.v_proj.weight": "blk.$1.attn_v.weight", - "model.layers.(\\d+).block_sparse_moe.gate.weight": "blk.$1.ffn_gate_inp.weight", - "model.layers.(\\d+).block_sparse_moe.experts.(\\d+).w1.weight": "blk.$1.ffn_gate.$2.weight", - "model.layers.(\\d+).block_sparse_moe.experts.(\\d+).w2.weight": "blk.$1.ffn_down.$2.weight", - "model.layers.(\\d+).block_sparse_moe.experts.(\\d+).w3.weight": "blk.$1.ffn_up.$2.weight", - } - - v, ok := directMap[n] - if ok { - return v, nil - } - - // quick hack to rename the layers to gguf format - for k, v := range tMap { - re := regexp.MustCompile(k) - newName := re.ReplaceAllString(n, v) - if newName != n { - return newName, nil - } - } - - return "", fmt.Errorf("couldn't find a layer name for '%s'", n) -} - -func (r safetensorWriterTo) WriteTo(w io.Writer) (n int64, err error) { - f, err := os.Open(r.filename) - if err != nil { - return 0, err - } - defer f.Close() - - if _, err = f.Seek(r.offset, io.SeekStart); err != nil { - return 0, err - } - - var f32s []float32 - switch r.dtype { - case "F32": - f32s = make([]float32, r.size/4) - if err = binary.Read(f, r.bo, f32s); err != nil { - return 0, err - } - case "F16": - u16s := make([]uint16, r.size/2) - if err = binary.Read(f, r.bo, u16s); err != nil { - return 0, err - } - - for _, b := range u16s { - f32s = append(f32s, float16.Frombits(b).Float32()) - } - - case "BF16": - u8s := make([]uint8, r.size) - if err = binary.Read(f, r.bo, u8s); err != nil { - return 0, err - } - - f32s = bfloat16.DecodeFloat32(u8s) - default: - return 0, fmt.Errorf("unknown data type: %s", r.dtype) - } - - if r.repacker != nil { - f32s, err = r.repacker(r.t.Name, f32s, r.t.Shape) - if err != nil { - return 0, err - } - } - - switch r.t.Kind { - case 0: - return 0, binary.Write(w, r.bo, f32s) - case 1: - f16s := make([]uint16, len(f32s)) - for i := range f32s { - f16s[i] = float16.Fromfloat32(f32s[i]).Bits() - } - - return 0, binary.Write(w, r.bo, f16s) - default: - return 0, fmt.Errorf("unknown storage type: %d", r.t.Kind) - } -} - -func (m *SafetensorFormat) GetModelArch(name, dirPath string, params *Params) (ModelArch, error) { - switch len(params.Architectures) { - case 0: - return nil, fmt.Errorf("No architecture specified to convert") - case 1: - switch params.Architectures[0] { - case "LlamaForCausalLM": - return &LlamaModel{ - ModelData{ - Name: name, - Path: dirPath, - Params: params, - Format: m, - }, - }, nil - case "MistralForCausalLM": - return &MistralModel{ - ModelData{ - Name: name, - Path: dirPath, - Params: params, - Format: m, - }, - }, nil - case "MixtralForCausalLM": - return &MixtralModel{ - ModelData{ - Name: name, - Path: dirPath, - Params: params, - Format: m, - }, - }, nil - case "GemmaForCausalLM": - return &GemmaModel{ - ModelData{ - Name: name, - Path: dirPath, - Params: params, - Format: m, - }, - }, nil - default: - return nil, fmt.Errorf("Models based on '%s' are not yet supported", params.Architectures[0]) - } - } - - return nil, fmt.Errorf("Unknown error") -} diff --git a/convert/testdata/Mistral-7B-Instruct-v0.2.json b/convert/testdata/Mistral-7B-Instruct-v0.2.json index 1da4d2ad2..88d447b3a 100644 --- a/convert/testdata/Mistral-7B-Instruct-v0.2.json +++ b/convert/testdata/Mistral-7B-Instruct-v0.2.json @@ -4,7 +4,7 @@ "general.quantization_version": "2", "llama.block_count": "32", "llama.context_length": "32768", - "llama.embedding_length": "", + "llama.embedding_length": "4096", "llama.feed_forward_length": "14336", "llama.attention.head_count": "32", "llama.attention.head_count_kv": "8", diff --git a/convert/testdata/Mixtral-8x7B-Instruct-v0.1.json b/convert/testdata/Mixtral-8x7B-Instruct-v0.1.json index 0967ef424..a15965324 100644 --- a/convert/testdata/Mixtral-8x7B-Instruct-v0.1.json +++ b/convert/testdata/Mixtral-8x7B-Instruct-v0.1.json @@ -1 +1,348 @@ -{} +{ + "general.architecture": "llama", + "general.file_type": "1", + "general.quantization_version": "2", + "llama.block_count": "32", + "llama.context_length": "32768", + "llama.embedding_length": "4096", + "llama.feed_forward_length": "14336", + "llama.rope.dimension_count": "128", + "llama.rope.freq_base": "1e+06", + "llama.attention.head_count": "32", + "llama.attention.head_count_kv": "8", + "llama.attention.layer_norm_rms_epsilon": "1e-05", + "llama.expert_count": "8", + "llama.expert_used_count": "2", + "tokenizer.ggml.model": "llama", + "tokenizer.ggml.add_bos_token": "true", + "tokenizer.ggml.add_eos_token": "false", + "tokenizer.ggml.bos_token_id": "1", + "tokenizer.ggml.eos_token_id": "2", + "tokenizer.ggml.unknown_token_id": "0", + "tokenizer.ggml.scores": "e3d3eea80bb41a1213f2d0aa3e8a38581d1f19323be77dbd779c9c7e3b72e676", + "tokenizer.ggml.token_type": "6040635e6bd38d98af06698feb75c1802bad35180ee6ae0a503e38c0f60fd71e", + "tokenizer.ggml.tokens": "604ac4bfbd019e430d7b6cdf18c6c0cd5b967900601f0307f714ec7773aa5ca6", + "token_embd.weight": "1d1d1d39a867d5a4bfb32792a47247d2638c10c95a6259391d02843583505cc4", + "blk.0.ffn_gate_exps.weight": "2e5cd43ac3f26c44f071926ff6c3f239ecc52a34bc9a5b5906d3d4c1bf2fbbfa", + "blk.0.ffn_down_exps.weight": "a4dfc7e7c96e7402eb70279601675b956bb7331da8101e63fe5c0a611b6972e5", + "blk.0.ffn_up_exps.weight": "2d5d87b378b2319c344ed2c642598b6f7cb6beeb582a8ea51abc9ae690d473c3", + "blk.0.ffn_gate_inp.weight": "a46aaf5aba7401ce6e41f158242b4879d34901661f3ede85496cbd0ce79d6314", + "blk.0.attn_norm.weight": "3fe37d913bdd2b65076bcdd6efe64a37b0b03cacbb1b80b9f7089068aa35f38c", + "blk.0.ffn_norm.weight": "5e14308a3c894734eb204c8f558bdc817e94bbd5b4e9cb4094e91ba388c8f7f2", + "blk.0.attn_k.weight": "73d943dcac0911e87bd771f4aa1c901e1bfe1aed293af06e1a67812159859f67", + "blk.0.attn_output.weight": "4c5f754c855e262e8d4c94c6fbbb57af06399dc0e170d7d99a1a17fc9aab9227", + "blk.0.attn_q.weight": "d6fd7403c873d49c05f6f03208f30d99ad34cb3b71c9990c47334d502a8e4c7b", + "blk.0.attn_v.weight": "cf17cf64b2d683bd9de6cebaf60e5c264df6fdc38fe719dde9d54c80334f6366", + "blk.1.ffn_gate_inp.weight": "0d524de81cd915816b4e714bf595ad6946a9130b3de731cd89428b2781230809", + "blk.1.attn_k.weight": "2ea47f412992b374c70674730fe84700e0c8cce177086ce9b6635e42408964bd", + "blk.1.attn_output.weight": "b4b2520794d54113e86c8ff678eacfc62e35be4395a594a6c8c22b4383ebcc0c", + "blk.1.attn_q.weight": "5db930c98c4f91f6eab57eb974c72210b158e366d23d6d2890b2759c053bee33", + "blk.1.attn_v.weight": "079bdde09668394bf7af9f8bc175017b4f48f0ab64e6dd855a4d7561d1693c0f", + "blk.1.ffn_gate_exps.weight": "146a62de19f9ab093deb101f9640534ffc3dc40d69f508be12fc0475d01b0c7a", + "blk.1.ffn_down_exps.weight": "949da94a3c0f375160672a979e85f7def284264b10d48d038238aad5f5ece793", + "blk.1.ffn_up_exps.weight": "7016a3f467d9e3f2f4b4019579ed86b757469cd367f2b225483305376b4bb3c1", + "blk.1.attn_norm.weight": "1614d1e6ed537737275eb888666c7bac533f4eefbe73dec92b591045ca9e1afd", + "blk.1.ffn_norm.weight": "405a455fa7d1ec36894652ceb554bbcb09a07fd6405f42741e66dc4a4665c19c", + "blk.2.ffn_gate_exps.weight": "90d5003fc7421f44220c0842d43128955e91488f6f785fe570b62d81b719e964", + "blk.2.ffn_down_exps.weight": "ecdc2b5a8b504ef0a7833acff47d69b0c1fa9c22126de1bb120ff5e48c3d6e2c", + "blk.2.ffn_up_exps.weight": "2cbd9485a32460d315eb50a2f3b00863fd77245bfe885b7565efac1cdb1f191e", + "blk.2.ffn_gate_inp.weight": "0d0a17a1a2c7a61f2cca49ecbb479154dc93a870873257bc4f225e7607f2e2c2", + "blk.2.attn_norm.weight": "b2e4c5a977f87a6f880896bd73596234c9b83622fa0d7add5892501e3155913c", + "blk.2.ffn_norm.weight": "0ab875b4280afa922376cfc7b9aa3f7071c9432ea1254091ce7de3749df0e8e6", + "blk.2.attn_k.weight": "bb884af51fb51550acfef54ccf1b58ce8284e587806e6a2f88c8265e1ad05a5e", + "blk.2.attn_output.weight": "0f03099ba1ef342ea61af9cd71d028123bbd8b1dd7d7fd9b509aef77815427d9", + "blk.2.attn_q.weight": "8fad0d29eb4c9d24e564774ee3316b9eb7a4c4985e4567111d2c836c830f6cf3", + "blk.2.attn_v.weight": "fe04c847ff677632401a94e7b6b6fdca60391ab21cb23bd791533115de6303a1", + "blk.3.ffn_gate_inp.weight": "29e3aaa724590c070e614af8288939603d2641b0ef11e8c0f476bebb2776673c", + "blk.3.attn_k.weight": "231cc5631def10f7f292d8862d6125ff555164cd70480ac76362149fad204497", + "blk.3.attn_output.weight": "86467a605c62852e05fda1a7ef43150df2cf715fe59785dbcba09f1c27cfa086", + "blk.3.attn_q.weight": "901822402453922225c2d6ac79616691d48217635d5ff7338daa971d5ddee210", + "blk.3.attn_v.weight": "27030784f44375720df2f090933645a31a022d3fb3b14573e5ca0b78f44070c1", + "blk.3.ffn_gate_exps.weight": "231ba59cc0b988d125d77bf627aa3f04636684870af88f081f3944b48a160d86", + "blk.3.ffn_down_exps.weight": "530c3ab44ae4d66e8afa4d10c153ba5dfcdfb7321989a988e62e9d12e7234625", + "blk.3.ffn_up_exps.weight": "b85c2d4d9d11332e702b3c0a6610d4f525f9a93e5d12f5c7c55c592c40755e75", + "blk.3.attn_norm.weight": "05dbb6d88cfa6b199f9d705ccbda97c0ef13f9ec875c595398a1a42d009a4555", + "blk.3.ffn_norm.weight": "6880b1c27d46969ce36fac049c05dc8b89e4bb47dc89df357e32df7e18fc512e", + "blk.4.ffn_gate_exps.weight": "a883b4f225b760c5a2f6605dc5e2167ab85bb398c70bf64ceb539fcbd6128dcd", + "blk.4.ffn_down_exps.weight": "d291bb656aae77947d4b525e2819bf4112afece53ff31de9dab999af1f65f9c4", + "blk.4.ffn_up_exps.weight": "38592afb8ba3dcfb26970f906174f7d3fa62da44fa4be4fc6912a19030ea9164", + "blk.4.ffn_gate_inp.weight": "1596cb74e8fd6c3080b937b06468bb397b0dbb661e6d180a6bcbdc43e8bfd0c6", + "blk.4.attn_norm.weight": "f90c83c5ff4366281d283384efc941620542b9cfdea160d678dc54a75e33f758", + "blk.4.ffn_norm.weight": "d28d8c49d1746b7cc085562d1074905fd14023844de823dc4fb22202bb280790", + "blk.4.attn_k.weight": "792bbf412cc357140fdaba543e547a9b2f7582919e307bbd9a80c7d6d8f5f1f9", + "blk.4.attn_output.weight": "d98e4a062d2631d9c315f1990d5f6ca9a88e7e0e46387f611ccb0353f876aa12", + "blk.4.attn_q.weight": "1a11a55a91d9f748a72176ff6b1c174844df406e00d1b66b9aa64dc6ee4bcd1d", + "blk.4.attn_v.weight": "04cb3c02b12a6313c7ac7044513441083d534fb4c5a3f63bbaa58f7edbd2fadb", + "blk.5.ffn_gate_inp.weight": "cbd5cdf015d33a2da6703eb74c22fcb97581fb9175435173b6dc4f9e8364320d", + "blk.5.attn_k.weight": "4fdf3405e4d657403f5647b51233521310ee984b4b81bbcd901cb3e6ab76b7ff", + "blk.5.attn_output.weight": "4a25662c46979a29600ed77e1907cf81fb16ef30e724c155444e54ccb76af481", + "blk.5.attn_q.weight": "e2acb30e30b97300039bb20ad0878f05159d5657fa811748a51d5b6fb35d631e", + "blk.5.attn_v.weight": "306504b6a26aa123c63dbbed3f4ced0ed2ee8fb6a30bf0093539b817539f5ece", + "blk.5.ffn_gate_exps.weight": "7e34df9b9944dbeea5e8565786d3aa6937314a4b87acd4d0874687877c5a39fd", + "blk.5.ffn_down_exps.weight": "c4b7a57a42b5ac0a8ae27dcd5cb2646d7a7cc7123126d44a56ab128e85f60b13", + "blk.5.ffn_up_exps.weight": "09d47593b6dd6c664a9155bff02fc2eb7ac4a70219a88162d05c802a01d3c6ba", + "blk.5.attn_norm.weight": "58804a036d6ac4c1fe357b8b6a97a5c37cae1c2f06ee0086c041d449c1c6ef6a", + "blk.5.ffn_norm.weight": "d872dee6789f0826211aa46ca9d0869e3e96bcace9e77d6559a7b6f3e524f3ca", + "blk.6.ffn_gate_inp.weight": "fb1eae732e974d6c1d020a5b4ef98c5f33016f984701bcea656f999a99daad66", + "blk.6.attn_k.weight": "55e9c59c5051ab5519b3a7962e1b5fa96a3c0251cb6200dc2f177885ad2de470", + "blk.6.attn_output.weight": "f3c834a8d0027370350e2b6294d95434d31432e57be6313b013c15a56303d61c", + "blk.6.attn_q.weight": "efaefe5f11c2140dc7cb532b0832c2a0b363a165cbda21f00fadae77efca377b", + "blk.6.attn_v.weight": "900bd734d75616d846a90a121c97e081c956a3d1ab012f66dd0bc62c43e1ec3c", + "blk.6.ffn_gate_exps.weight": "312a99661b1468fcaed2474621116f1681432755e973f3ee79d01912974fd424", + "blk.6.ffn_down_exps.weight": "ac9cd7db67a2ef0d2b5def86873673d05e48d49d147dd944469dbb8e2d4c46f6", + "blk.6.ffn_up_exps.weight": "57613e7e09579400a1a09fee4445acfbfe83f2f327fdf317877787d96ada6b84", + "blk.6.attn_norm.weight": "0e8801e09885c633bc01a9a5b85d4e878d30158a4eb41a937dc5b760ebd044cb", + "blk.6.ffn_norm.weight": "b8c58062ac93072f878446b0e7f958c737aa47fb769fc3a8f593133d12db2dd1", + "blk.7.ffn_gate_exps.weight": "1ef611732ff13edfa8d30981ed9dac00c15ceba9fc012ed0b199e9280a849948", + "blk.7.ffn_down_exps.weight": "856c6811945c7b0fa461ca17811cfa43436b4cdf5326bad23cbc30883486d7cc", + "blk.7.ffn_up_exps.weight": "6725e3e33994302ee13fa5ec163631ce2dcaa08aadde8fc166c2265d4561c5c5", + "blk.7.ffn_gate_inp.weight": "36b49d7f80c1003dc392b2c1b9960cd49889dd69e77b26b9e4b13d01f3d0a32a", + "blk.7.attn_norm.weight": "7a0ec49acc5e20ee71c6f80ca02f4f1e564c485e0ae0621309e7c2eb0c616cf0", + "blk.7.ffn_norm.weight": "eeae035c39ab6e64bc06a4baa1bf6e50d4c8b8797cb0ad8abd48be86974802c0", + "blk.7.attn_k.weight": "e8f78c1def01a7a38d2d9bf7becb17755e28fefe4927856f7890fbee52840187", + "blk.7.attn_output.weight": "5367f05ac3bb49ef8745ba5902e1bdd4442415a3ebff2c7e1a3918d7be6fe948", + "blk.7.attn_q.weight": "37c95fc5acc55a4f6e5f02cab9be60e4fe54c08b65f98f4455741b4aa542ff4e", + "blk.7.attn_v.weight": "c89f1343486ba55814233511e94090f7365662a8a4214aa4c278cdadc79196c2", + "blk.8.ffn_gate_inp.weight": "4e239afe8c7afb8de3a005757c887cf14b1622ca2d224227591cb0e5301f4c17", + "blk.8.attn_k.weight": "2ad0229f30fdcc1e85ce64e00d8f75902238294844a81d5af43e14ba75c02983", + "blk.8.attn_output.weight": "2e44a4722acb3b521b81d0b910f8ca2f6c286d874a92ddd02150566454061699", + "blk.8.attn_q.weight": "1cd2b09cb2f43e08de776b5f7eac197a5a6d4ffdfd52b21baa36319450147bd0", + "blk.8.attn_v.weight": "5a22c57ebfd33ac500cbcfd321d5b5b1783f8728801db6f3f8bed51c7183e4db", + "blk.8.ffn_gate_exps.weight": "91063fe56cb4f3ff3b41052bb5046fcf8ef61516a603ee90aab893a9d68c15a7", + "blk.8.ffn_down_exps.weight": "d4c3abc8f1d1b462f67f70bd8f404b3fcf45dceeaa8527fa120527254c383c90", + "blk.8.ffn_up_exps.weight": "76a1a1f08ec577716a2e7027b45293e9205751126424f1bebe1de89c78f087d5", + "blk.8.attn_norm.weight": "f980d774da39eb76c52358afac3e38cb4c81cb323deaabbe5c41822e3f17a98e", + "blk.8.ffn_norm.weight": "1c937658cf90f1a85db9a5f26e077730fdd4b694607dbeeb825c5fb2bc407e0b", + "blk.9.ffn_gate_exps.weight": "a2532471ecb7896d5c78e5a34e10cfaf4125265e1595166c8d0d0dfbe2a3187f", + "blk.9.ffn_down_exps.weight": "b47921a28412d48fee450b8b9d97cee42344a2e69f06d407fd9523d7adf13333", + "blk.9.ffn_up_exps.weight": "7c461bd1b2a73b439cff6a10d94afa01e8b06f7e6f09d9a6f28e3876aef48bce", + "blk.9.ffn_gate_inp.weight": "1648dfb08b5c06d7953a5a97ecb764995fae9487fb729a1c867023b2538149d0", + "blk.9.attn_norm.weight": "8635db0f299882a63b7cfcd1d4259c9e53fab22c31d3d054de36b1001380b31b", + "blk.9.ffn_norm.weight": "f9309aa323062d174c463613afef9b0a33501b510bfaa58a8e0e866d12ffef3c", + "blk.9.attn_k.weight": "dfe62030441e947a588512d18d9c6e4ed72c2f71c227d622c095e4263b23dadf", + "blk.9.attn_output.weight": "1977beb75c6349c50ba7dd3865d7c0a9c5c5ddc854413147b0eec98ac4fda351", + "blk.9.attn_q.weight": "eb132596719605cd6bd1782487f121994629e115190edd69240b12af66e734f5", + "blk.9.attn_v.weight": "9e708f15d332d7c5187b0693b1a977eb30a2fa10bf7df48ed9d7537c0aa6ed99", + "blk.10.ffn_gate_inp.weight": "97503a5d166c1925f9b65c0eed980753d411714d66896f3d0fad5286c7aba702", + "blk.10.attn_k.weight": "1ebdd222336bd25b48df1b138cdbe09021c4a5562ea7cb78cadd1255d2be3a39", + "blk.10.attn_output.weight": "5e98faa38e9d514b9057e1c8342c509cbe1083defd518e506f6bad89117d1f5a", + "blk.10.attn_q.weight": "3323a26c87d936d1dd87c577d0b763459fced726679612c874b3de5fc6d969c5", + "blk.10.attn_v.weight": "d5fa73cb56aca388e205f44455e4b4f676fdc12ed7fac4542fbb3b41ecea59ad", + "blk.10.ffn_gate_exps.weight": "225021b53782800906cd13b70be3a4161e8b300b97f984a959ccad6a6e8adcbd", + "blk.10.ffn_down_exps.weight": "f08eb91526bd22f5fd0402fe925d6141cdbb308a1ced0330858d0c85c71f5ef3", + "blk.10.ffn_up_exps.weight": "a9f688350c3b53eaada5103b5848bd9a3d7d6b327a70fa16c24bf28ece933eac", + "blk.10.attn_norm.weight": "5ba426c9dfc79805015ccd76cd1068b0ad3bb7a8453e14bb1d35486f122d8f95", + "blk.10.ffn_norm.weight": "98891d6acbc3986b2581b7a3af9f5946a392d9188972c6a8b15d4e745a4f2482", + "blk.11.ffn_gate_inp.weight": "b2365a60566e7dace892e1cb0e62eb73ce387352601723e847052b34874feaa6", + "blk.11.attn_k.weight": "0efbc1d1430505543ff71532a4fcda821aeac616ef6c1dca40e00d4f2ff70bea", + "blk.11.attn_output.weight": "3d5bd4d9a41236f30d4293edb9ae27beaa113ffb31b4fbfadff3a4c370dfd3e6", + "blk.11.attn_q.weight": "aa11e9db14dd9c77951511443077c2a1a78070753d7bd3d9811038473f69e325", + "blk.11.attn_v.weight": "5adc567f377aa11d1763d35f50e53fb2896a8b03b623ac36acc45efa2486d512", + "blk.11.ffn_gate_exps.weight": "71d07d982aabfab9eed3c733d49c20f023bf475368fc71db5084d91beadc4b47", + "blk.11.ffn_down_exps.weight": "9a06e61461e48b3925a9f7d9cca634d048c8b62163d7bc5c43e35899f959319e", + "blk.11.ffn_up_exps.weight": "bc05494d0dcec61021b3ac0c5bc1bf502736cadf48224e213bc139d562699a89", + "blk.11.attn_norm.weight": "a5758a10bdd0404ae1470e8e9db903985d4d07f60553c5001a5e7b660d4f7ada", + "blk.11.ffn_norm.weight": "814ae037563aad3771787316bec4806c95bf6f5991dd6474b4b1e5cc13dc18ee", + "blk.12.ffn_gate_exps.weight": "3a68b831ba1606fb9ef6dffed4732032447ecef23ea563ff4e79317586c7eb49", + "blk.12.ffn_down_exps.weight": "268b25e13f4b7beab08686e83705a41b21d15251809ee4784526f78a580da829", + "blk.12.ffn_up_exps.weight": "9105751a5b5b42ca2614d0456f24f779d2e2ac8cdff0f96842aa7ae2b70f341e", + "blk.12.ffn_gate_inp.weight": "d0de1558cc1d458c5c504f63ddc59785c323df7330474bb0644c346104b40a3a", + "blk.12.attn_norm.weight": "859a4c8113678e2e202d10299850e0cfb52eb11ea50bcbf4fe3ff39bdd394154", + "blk.12.ffn_norm.weight": "7fbf4c459c1760218877e9ee3f5ad49e960956a4369bcfe96c143f04ff9ddf97", + "blk.12.attn_k.weight": "0a7e254fdf3730a57372b6ff421a613eabaea68cdefd64800857941411318374", + "blk.12.attn_output.weight": "ceb763fc15d88af149d8fb78e82db2b7dab3aeae584af8cf7611a12356a397e5", + "blk.12.attn_q.weight": "a43402d23c46cb2d3cb3c2a98c81b19d10026b7e6742370fed6b2880b6e049b5", + "blk.12.attn_v.weight": "3bc24f2c0480ce91ef72993ee8f1cf962f7359e12183424583ffa1246bf3db52", + "blk.13.ffn_gate_inp.weight": "a6d68c82bfe66d8bab68f980f5f18268a9e2c0cd6b8832ed39010e0de198ae05", + "blk.13.attn_k.weight": "0166c39546b37dc2e01b2b396ba43e183f797dd04eaa51a6d103d8b58ee4bace", + "blk.13.attn_output.weight": "2ce5eb198deab9557475a58b69b11e9874b547e05c23f223c6e42fa35ddca069", + "blk.13.attn_q.weight": "745c1bbdf434284a7fae98f45e821c076dd9c2a2467dba6a9d8cf0041e419dbc", + "blk.13.attn_v.weight": "9ece68d5ac64d1421ea7aa32e1cff9cc1fecf5175f4c4da858dd31d8633e3337", + "blk.13.ffn_gate_exps.weight": "ccfdcb4670b131689de12d396a010b5ea737795cf5c15a14a304d720b3c7c899", + "blk.13.ffn_down_exps.weight": "8b8fb328664764f1aaa5cbdec336d5654e981e965a02ef622bde5f07ea1c164d", + "blk.13.ffn_up_exps.weight": "d2ace0236c2fb3365fdc85499d676a7f65813c48e5085348b1df1799922766ec", + "blk.13.attn_norm.weight": "1ed29d7d89ce52d7cb4d57e895ff7115430466e917136c049c385c030ed44e9c", + "blk.13.ffn_norm.weight": "a194fc542597a4dcfdfaec5e3cba2a2b2b21b21edfc87c39c0d7f7651355bc4d", + "blk.14.ffn_gate_exps.weight": "a625e3574e5e740e7f8e2f9c40390f2f382c720aab5b10534e298002dd8d1fb9", + "blk.14.ffn_down_exps.weight": "bc366f015b83c865946afd74c8a884943e0ea2c671314a0b7bb72f21a44d2f78", + "blk.14.ffn_up_exps.weight": "ee3199bf2086de77b49f57f487676be8ee70e102a2fb5a5ef8ddbbc28a9eff41", + "blk.14.ffn_gate_inp.weight": "2b437870c850fa2e2044d032bb02908af634356e37466fdae260b933e48ee8b4", + "blk.14.attn_norm.weight": "cd8344d193a1cbd42bd898e17f4bcb1ca0b2918420fbdafa9249a6f2b7f4ae06", + "blk.14.ffn_norm.weight": "70eec40374e558fed5b07257283cf36342b6b0129285a00007deb59c32c9f7c8", + "blk.14.attn_k.weight": "4053bdb507e0543d724b632570bac86b31707696d90a0db44c49b2a082e0d599", + "blk.14.attn_output.weight": "0182632cb0e06a07241b8293d25d109fbc1862e1e337d435f908e8681e2eb1ab", + "blk.14.attn_q.weight": "ffc7794a4c1b6f793c842dba969435330a7a80b9212e457b4b2ac33e68b41241", + "blk.14.attn_v.weight": "6411805292d528e61bbaad8f9aab9dd073529a17946c057fb06864fad9cf3211", + "blk.15.ffn_gate_inp.weight": "77d0744567c76e6abb67f81ba9c715b2b544841186d5b948309571eff213bafb", + "blk.15.attn_k.weight": "1f7957954ea4c6521c257b35a360e868ffa02bdb3de91f146d5e06bb4a545c98", + "blk.15.attn_output.weight": "d7809d36bd8d3342240c46fd87bcc7f9821a222f48d9a95e45ae50460265d3cf", + "blk.15.attn_q.weight": "25f509313ae4d8401b871904059f472a26f5714e7c791c725de77a1a522c976e", + "blk.15.attn_v.weight": "96fedf5a591fc0f020e6de10fd72ff12b3ef9cf70cd21dabaa0d3e7b06f54e73", + "blk.15.ffn_gate_exps.weight": "8f950d976b2fd9a3d213b84123cf114c1377efde9352767fb2ddee89e177c8ef", + "blk.15.ffn_down_exps.weight": "6fd09d1557bb94b06efbd4f6a1ca4be532a202ba290e9315bc8da3d12a5c4c4a", + "blk.15.ffn_up_exps.weight": "cbeb59ae7b0266a928dc7e3a6e70a9330b92f9ee1b17ee1ed91022108204a33c", + "blk.15.attn_norm.weight": "2005330911ac2edc7b6d27aca021c67d30d16eb632e49b1a13f30fdb2717aed0", + "blk.15.ffn_norm.weight": "0e9198f3b548eb78acc8961f2b3350d238d26cec110933ba753a8cf0035c501c", + "blk.16.ffn_gate_inp.weight": "a41d1f99d739c8b150c3945b6949763988d0c6a4c5a2b5855592ca1a48ed23d5", + "blk.16.attn_k.weight": "b624e2ec88c2d3047f60530fb87e72cb4a5e655a9663f6f3e9b09e5ad32cddaa", + "blk.16.attn_output.weight": "687759ea75e45108526ffc1573d6fdf084728079bfc2dc89b9979e76280f43c4", + "blk.16.attn_q.weight": "beff3a45c7e9ec82ffc6d3c701126be28654d10aabd747d03441210491fd31b6", + "blk.16.attn_v.weight": "43a349b13f0b9d040cacecd942bcb168c030fef8c75c987d59a4fce6c14e855b", + "blk.16.ffn_gate_exps.weight": "793406d6c13d727c82bb7b692ca98d65ca975baee69fc57be5378d77c5a19b62", + "blk.16.ffn_down_exps.weight": "9bad3dd150d0230404b7f886ac7ff8803225757e813f195cdb26bad245243b4d", + "blk.16.ffn_up_exps.weight": "7449d663023fea3496475bf0a9c1de7272ad0ce9adcb3265e8e424badaa674dc", + "blk.16.attn_norm.weight": "a424ce34c195a401df1ce37ac4f2794e8a6720b1ee8acb21428e2b68c65e0125", + "blk.16.ffn_norm.weight": "405a68bb8e16e1064df2de55ca3cd9ceddda1d9fc0af007a9bd7cad4b2676248", + "blk.17.ffn_gate_exps.weight": "97c6e5321491ca5dc039ee88da0eb0e78f347372785411809af84b3298cb19dd", + "blk.17.ffn_down_exps.weight": "1617ac19788a1be19bac69277408761e6bdf5719d63a8c7fea14d41cc27641b5", + "blk.17.ffn_up_exps.weight": "4ead1c365f112581c10610ea3f63d2a1474311d2503d2060fed4b458ef337f5d", + "blk.17.ffn_gate_inp.weight": "ed4b3393f2523f2b5e0fc7680a1caa2842e605728a529b5af68a7fa8d7abf940", + "blk.17.attn_norm.weight": "beac17ef86a7fb2b5840cc72f7a95a5e3d6bd24e7fa698e0b0ebb9bdac45c561", + "blk.17.ffn_norm.weight": "81cb58ec6d6dc02a0b4ede10adc336dc865fa76f982d4eab0e4a37b40f5b0fac", + "blk.17.attn_k.weight": "eab569e5ea8c8b05e5a6a209fba031129453c2e28181eee3e736b3b04b36bbec", + "blk.17.attn_output.weight": "f85b70f01438ce8fe5d10599b113f30bf18dee2bbae0657d3eba295870001db3", + "blk.17.attn_q.weight": "887ceebfbf6a2b94b43d2df4439ac3a5bbc29311d4b28addc04d525546032047", + "blk.17.attn_v.weight": "2df9414d65014c06a93da22ba3a668be7b83e2e8008e98d7771f7dfebed98298", + "blk.18.ffn_gate_inp.weight": "9b07741a0950fc667e5fd25937e33bc22e1f764f80eb4ff3119f005327ae0f6e", + "blk.18.attn_k.weight": "8649598dbb63938744c39bcda5ce8c31773e29c573be8d4d2c114f5030f8d3e8", + "blk.18.attn_output.weight": "f8e391adb92622298ca834d5d1eda48b69c3b1c51c5a584ef6c54a725c298d75", + "blk.18.attn_q.weight": "84bf8708a2eed618f48f69c178ed7dd11fa4c468102376e72e910ebd037d131f", + "blk.18.attn_v.weight": "31db3cd773f09548c2c1b1eac2718e46364a7810970fe9c433fad9d8de5397eb", + "blk.18.ffn_gate_exps.weight": "be2a2ba378002f1b61f86c273a69eede9b93786d5ce96b4fee1861f730dca4c4", + "blk.18.ffn_down_exps.weight": "d35196159e37705db50a5343e3989f7335477f1a4add67ef42ad64a638cd07ae", + "blk.18.ffn_up_exps.weight": "c6ceedd86e97913a6dcadc838e7abb762d629fb8dd55f15cf02fd9bd66d2ba78", + "blk.18.attn_norm.weight": "41f0b1ad83d6e3cb9fbe0d27878c2e7ad4a351b9f554a6bc9117c01745cdf6e5", + "blk.18.ffn_norm.weight": "96646204bd0d82f25dc77faba4dbd86b1332e449313e6684e00122da8be99057", + "blk.19.ffn_gate_exps.weight": "c6eb7f61e7938bda0492dbc05e51e8f631c99224fe18e99861fc4fc53ba9e9ff", + "blk.19.ffn_down_exps.weight": "4384803da3a3a3d44120d7dd192fe2c9bbd9a1a0cb492dbec1fdd7565230f1e8", + "blk.19.ffn_up_exps.weight": "22d73de2fbb8bb0f1bd2caf17fad8a355c47d914143f7f6e6d0128f66f074a60", + "blk.19.ffn_gate_inp.weight": "9a0cc4a2301a5634022fbce41189021bf0d1a961792d2d9330fd35556d18e5bd", + "blk.19.attn_norm.weight": "c5cc56ec5df9a1f7d5ad71fbda49f1433132e58895d45cb44c73420bd61ebd6b", + "blk.19.ffn_norm.weight": "77e17de741742ef2482fc7872fd423c8e3c1454dc4d2be89ee939084b6d78bc0", + "blk.19.attn_k.weight": "a92ea36ce2e3569656306aeefb835ccd5d1b03b33a86e0d3d030644cc923b813", + "blk.19.attn_output.weight": "5e2a912b37855f84ea964907a1a86d609cbdd79efa0c93c3e8e2fc07caf7c226", + "blk.19.attn_q.weight": "4ef3a5913292ac3c1a6fd3e9e53d011021f2b41d0276cf849706d1ca925cf7a7", + "blk.19.attn_v.weight": "42981b75b68ae852cee638b5433605c147da4392aaa6d7a06e756115b0171f39", + "blk.20.ffn_gate_inp.weight": "71381b9879a7c80b9f7b475abc0aa31b8cd71ccc00856ebe89764a2acb9df2dc", + "blk.20.attn_k.weight": "1928b7ebc054eb3967929ed6fb446314d5352f4aaf8b475ce55c6345019f2ea4", + "blk.20.attn_output.weight": "6071ecd9ca91af0d2ba93fef4a1a56f3b243dd70f862a21a2d164d56f386043b", + "blk.20.attn_q.weight": "002e95042a40f36ceed5829e3d0c8072e5f5e4ee86a089e2902b2348fed24dd5", + "blk.20.attn_v.weight": "42f509cdb1c0e298f89f896e349be86952c5168e49b3f83bb17badbcb7596d57", + "blk.20.ffn_gate_exps.weight": "a684a3ffe4b0a57c819a5fa9cb3521de223f392732927271e97ce925b6e33765", + "blk.20.ffn_down_exps.weight": "e3081a7bc7ba750d8a4886bc8ca4f231b55db4ca082b54b4106c7531964725cb", + "blk.20.ffn_up_exps.weight": "fad0fd5eca36ab154788da28be8ec25bb5d6db06c9d133db89e96df358a2f6a2", + "blk.20.attn_norm.weight": "c3e3f2429715ae95e884ef1246b0b461b23c5cc0ed08beecf70a14cddd184820", + "blk.20.ffn_norm.weight": "ff31f609dda65ca496b0584fabea6550e42edd05ebf229812aa6b7bb5ede15e6", + "blk.21.ffn_gate_exps.weight": "366f09ef0ecfb86808eb3296cc9abdb957951d27f6533c03f1422b54061da660", + "blk.21.ffn_down_exps.weight": "3fc495947d27fcca7fc0893c8a96e5d48ba27b2c8c58f8fcfb8dcfcd5539741c", + "blk.21.ffn_up_exps.weight": "6713ed51410bcc8283cbb001c4ad784098f25701e8021f4fa4f411e186859c4a", + "blk.21.ffn_gate_inp.weight": "6d4c92c01ec801647134d907bf1108878156df266a6107abc10526332b328b93", + "blk.21.attn_norm.weight": "27605719ae2df24f4f2e85a730927cab20367631612cb501631f6bbf38eb1209", + "blk.21.ffn_norm.weight": "ca80ee8177db185b15a4a378c1cb6f7143c76546a7f1726bda23f329323d4ffa", + "blk.21.attn_k.weight": "9e49f743d4a5bda9b4bd9c40c2ca37cdae5aec7e54cb193897ac8b4945ada14d", + "blk.21.attn_output.weight": "ab923540879753feaed152f5950f69cdd83d8f2413ca873f5f038b63ab0aea12", + "blk.21.attn_q.weight": "62617fc3f1c9d2aa672a4d91a121c7a91b92d145b65e75f0b06b4bb7c825dc36", + "blk.21.attn_v.weight": "15f8b2e72f8e8e992f2f6b3e93238a9d7be7bd6136f91c9d04b4b4cd0cd60369", + "blk.22.ffn_gate_inp.weight": "3ddb1773d9257b68add7a2a4e94dad25ed926803e02707863dd742ab9b2dc179", + "blk.22.attn_k.weight": "680e45a9e8d5feddee5266e119dc053bf80718fa9af1cf6803e6f493b265f1eb", + "blk.22.attn_output.weight": "0d5fae3402fb2c5aa3a860010e3973fc8e3168d1015f7a76b7b2964681693206", + "blk.22.attn_q.weight": "eee7e3d426ab533bd18d62c9aa142eedbde394bed07db58313e0fccc82a23237", + "blk.22.attn_v.weight": "26b5be1fe3c2b6824c5a648a3e4bdf17691904526fca158fbc3ebb627b67e2f4", + "blk.22.ffn_gate_exps.weight": "32ab7a7735313d60f6a75229b1aeee940b6aee176c9648536bf5921b0dc2929a", + "blk.22.ffn_down_exps.weight": "67590808f6a67777d3eb7976c31fe616d388b98fecbb12253b72d1241d70753f", + "blk.22.ffn_up_exps.weight": "fc245c0183e6d90829ff5e71a4ec93e4860b3d4c1a17b9dda2fb64f5f5c9ed32", + "blk.22.attn_norm.weight": "128e99d206d4d6724758ec97468af767fa0aea592149c324b731659c1e74a1a8", + "blk.22.ffn_norm.weight": "e45f498033f0cffa15da0eff2c47b4472e43fcf8921729fc4eeb2e3a6b3c78e2", + "blk.23.ffn_gate_inp.weight": "d63e686f5325fbc89fa242c2c52a3b8ff54f867dca914c9ae6eea13e9d6f46e5", + "blk.23.attn_k.weight": "f71f5a577f46ea12b1818f3a5ff4b85ddc45f9a2afb0fa2e041d71a3e31c6779", + "blk.23.attn_output.weight": "92b13563c1e0eac0d748fb67b235dfd7a64c8f16e2dafb316885744582e23b4b", + "blk.23.attn_q.weight": "2f9b9c35dc4f912f3f51c06e2d68f417b51a0de0a84aac530a64f9d3d7b0a2dd", + "blk.23.attn_v.weight": "268e40813806e74a5c364b19556d087bf8374e76e7b6fcf55c381eb7da13ccd1", + "blk.23.ffn_gate_exps.weight": "12f857e7a7ce228afac34d99b602c8d6fe96984f2a21118f459a58cb767ee65e", + "blk.23.ffn_down_exps.weight": "cdb082c16599c3bb36a28066dcc122d9529b54fa91b6cf0153437ec960a5e16d", + "blk.23.ffn_up_exps.weight": "f4b99f6f44d7b8b5a305894e88633bf5938fc1f6303a2b2092399da9c8b64d7c", + "blk.23.attn_norm.weight": "a691392210383915916b4d3886d5e4d56e7855e27e37e414fbd73bf66b3712e6", + "blk.23.ffn_norm.weight": "0c3dc72f667e5ae19b69bfa9f2bd2a01a57681f89ef9527bad4eb0d8c7b70da8", + "blk.24.ffn_gate_exps.weight": "86baca2a3157994df7fd8ced5e08436d5c1810dc29c0715637c36de723e0e7d1", + "blk.24.ffn_down_exps.weight": "ac5d559562b35c34993e34b071f66d15c65be5907797078c2d2a49aba54e3192", + "blk.24.ffn_up_exps.weight": "fce0a099cf09777f44fbab3606ceb75f7fae6f0b80725f9e871654b8cdf9262a", + "blk.24.ffn_gate_inp.weight": "e7c6800c0cfc56b565b2d35ad6f1dbfdb70dd0b05b338bc8da2286ffc3678d79", + "blk.24.attn_norm.weight": "dc6cc18ec52d102d015153c4a1132f9d7a504e29cbdec81c5edbf3b9e65815e1", + "blk.24.ffn_norm.weight": "480d5a1397af5e0e657f1e67d20ec0cdef5724e71246a326843321b87ffabd33", + "blk.24.attn_k.weight": "338c0597954a9b95a782545b2fe36469553e73f86ae2d2b5697767b28e1c7daa", + "blk.24.attn_output.weight": "a77d23b79933c67e52f1eef7f83a3dff4f767ce0bbcc39572f8cec4acd457643", + "blk.24.attn_q.weight": "45c9478593002be1998e96e70668aafa2dd3972380fbc1df12fb05c24ba959e0", + "blk.24.attn_v.weight": "515729420885408a6a9614bc27cda393ed907521318d14d21335d39a3eff0b61", + "blk.25.ffn_gate_inp.weight": "aae4ac40e9ab3925241f9d784b54b38851d9bc999a6c3bc03fc3f17c9b28a67c", + "blk.25.attn_k.weight": "4ab4808d02396c35b00b426f536015673b71c17ae6cd55bbc2e6bfe7a4c59d0c", + "blk.25.attn_output.weight": "1990bb982b77e0c947cd1a8ef0b36227ee1259e6dbbc2829e5c136edf88675eb", + "blk.25.attn_q.weight": "a1490f3048e8c0ec8784f8550c43adf5cc8d0f2f90131c934713fe4b1b015bd7", + "blk.25.attn_v.weight": "f15e53c6d45b3b6f58808fa968425d65e0b26b7f9b268127a77abb1227c67431", + "blk.25.ffn_gate_exps.weight": "656662447ff54f56ee80f78a1b9483f7efdc40f7375d0cd8a9c72ccf21f77e7b", + "blk.25.ffn_down_exps.weight": "db06f101bccbaef19cced0f6c185166e18202465f4a42cddfd535fbe5cbabb4a", + "blk.25.ffn_up_exps.weight": "584a7b02456f27fe1d8d3c7ccd21d426b6ea887795a3ed77f704596a1e3841d7", + "blk.25.attn_norm.weight": "8f0f3597982930fd237e9d609776c64f2b909a455b21678f83a7ebd4bbb83e64", + "blk.25.ffn_norm.weight": "3e7079c32582afba0c55e032f254adc18d2997705eec860185e9a6dd3d82f07e", + "blk.26.ffn_gate_exps.weight": "e70341691b583b86489812b29b77aa41eb658b1865733d6118da54c66e3bfcc6", + "blk.26.ffn_down_exps.weight": "5c1b812d11dfb064af816ced5ab6463bf9722eefdfc341b8a93705d5038fd781", + "blk.26.ffn_up_exps.weight": "e18118362ae54ef7432781c83884f9fb230a9d934e342aabeda8822ea5f71fb6", + "blk.26.ffn_gate_inp.weight": "cd1c5f6710166b9567c6b74c97b2348b191c60aa860958c6bc264ab095261dff", + "blk.26.attn_norm.weight": "71d087531af2520bda2e676c489e8529cef5db8aeea1eec0a937a8b4f2fa2e54", + "blk.26.ffn_norm.weight": "7f704e936fda28eb5c2cc339f0f6a5f78170b5aa43c01265b21668870d819c82", + "blk.26.attn_k.weight": "1cc62a0ce0ae251275d898c52c4a9fba5995fca10955d2011d10dd1a59e1afb8", + "blk.26.attn_output.weight": "636e881b1505f9cef656a4be98bec6a4765321d51f9bf1dac8933397cf44b765", + "blk.26.attn_q.weight": "89a3c4d202d7d6adebb9e0c1bcfd8b775f6456386f1be25e86e43acc949c1e16", + "blk.26.attn_v.weight": "ff2cc963b597cdf1a21703f3e7022af3bb4c65a34a19e19d9309a7c5e198b5bd", + "blk.27.ffn_gate_inp.weight": "6150139498fefe380bb99d11e72028da47a15ecb73dfc5b2774f726f4bed8f9e", + "blk.27.attn_k.weight": "f286eb9e5c56c7b801a497aedc40158c2a27877d7f9fb59b3fc67834798902d2", + "blk.27.attn_output.weight": "5dc3d3a05f9f7729509147fd09c16fb53f85f520cdab5cb69abf4bae3fd460c7", + "blk.27.attn_q.weight": "8462e40f86b24251960d6f35a9ea99b8793a01937faf1aec2859f2e5395dbb61", + "blk.27.attn_v.weight": "bac1a99e38e25953f8315f7212eb9777dc216cadb09b959977885ae62724ceca", + "blk.27.ffn_gate_exps.weight": "6a15eca7f0f6ecfd93db2e55c63875348ec4a78c4ff643ec46df9e958c0101e4", + "blk.27.ffn_down_exps.weight": "2e1c91247c4359e2073a8e5f26fd7f6426da7be3ed5bc65dcfff701f0a5022b2", + "blk.27.ffn_up_exps.weight": "65d6f5c553c9332085eae4aeadf25090b5d7768212ea7b08ed698102c21b29a1", + "blk.27.attn_norm.weight": "7fab8ae63ec8e91ce625cd130ab96d8427dad3a7413bb21b25ec5f408c5b9f5a", + "blk.27.ffn_norm.weight": "532720546b0fdcd423a02ca6e3e9d8aacb84b1b3e8269968f88a47fe2a69bab4", + "blk.28.ffn_gate_inp.weight": "a305ea58d98962d9dcf0c53ad2389b7acc8936fb35a0e3fc9410e7767cd49dea", + "blk.28.attn_k.weight": "8315e8a2e4f78dfdf36d4fc18fffc74bc95fe42c3ae4f9af2b6c874612c0f71b", + "blk.28.attn_output.weight": "9b5fdedd32d39ef46a22cca7cd5355d7b93bd07ea305f466a8aad6ca5a4f3778", + "blk.28.attn_q.weight": "4e8fb96997c30e231c437130f410d7c91d541a816f6c568b5f3bfdb4b8dece74", + "blk.28.attn_v.weight": "1fec739cf3bd7b4913f72ca358d4cf31391c304de44ac0ae31ecb825beaa7cfd", + "blk.28.ffn_gate_exps.weight": "9f259789d535e09268266b9a8020f32d6a6779966c909d91d3a10574f06238a2", + "blk.28.ffn_down_exps.weight": "516d3f8abaedb01b9916a4b67d4672159769138ef2850158bc1b32c41e31f0e8", + "blk.28.ffn_up_exps.weight": "f2f1d88d2c31ed588806fb5ad981d68f5134d7284c4fc022fd018de2eef437fc", + "blk.28.attn_norm.weight": "960fd005598deadaebd969996f4367a9dbfad90539a863674fe95730935acc64", + "blk.28.ffn_norm.weight": "e1993b37ced93d4049e9af2c47b0d9207d8f7e6f2cc3a52f57bef30bc806d805", + "blk.29.ffn_gate_exps.weight": "58927146338f443513337476b3cd30e6341742f096c2beb5890d400f10121298", + "blk.29.ffn_down_exps.weight": "03a3386e4f0b75a28c5608e23b2de8f0de25f21954e4aa7fc343431bde9db07e", + "blk.29.ffn_up_exps.weight": "6916b7490a7ae7b04a5d81cc1e7ac9b20c483434f3b186b12d87fe176bf1567b", + "blk.29.ffn_gate_inp.weight": "98e710e467a3d567abe4ce29d78b8e8dc033148762290c0c5e1ae4d78efd8c78", + "blk.29.attn_norm.weight": "4e64cb307d37be20d55f38c94faf7e451d11df5e60df347906cbaf9c5441be71", + "blk.29.ffn_norm.weight": "696c23a52f742679bd44440d687a4c44b4302d57f1e9dc5610d23374336187e7", + "blk.29.attn_k.weight": "e85253652fd6120c623634ba66b725bf7cd491318b54ccdad2c7df8851d64c0a", + "blk.29.attn_output.weight": "4f650a71efb150d1f24cd4d114d4187bf570ac424da3b92ea6455abdf1aea705", + "blk.29.attn_q.weight": "69fa7da901026ebcbbbc848455b425458b7e3295007d7fc093acf4b38e2166ea", + "blk.29.attn_v.weight": "17e2e7590b317b21f106de546aafd955579703d1e95d6aea044ee72ec3a514c9", + "blk.30.ffn_gate_inp.weight": "3a03284b4aa60d59d4a2ec86253469b61fc656372afca427cb77a5332fbcc62c", + "blk.30.attn_k.weight": "d518cfd0db9708e769eb1399e87ee49357dc54d5afdbac3d4c0ca46c64e789eb", + "blk.30.attn_output.weight": "9b44378714d784c5ef9ab604359091baca4e0ec222afa139b7f840eaefb371fd", + "blk.30.attn_q.weight": "cbb95365bbfbcad0c9cd99b4eebb5a5d32de68ce08e4063b5ec3e792b7548044", + "blk.30.attn_v.weight": "e7985c04fe1740e35a9598f43b67b0922b4fc2d00b68a92a9f917b82c3248de1", + "blk.30.ffn_gate_exps.weight": "8ac4bbd07935d98f895ba94dc174e5ad5046c3c222b53729d60f987c05e7eb70", + "blk.30.ffn_down_exps.weight": "dd672cc71e82abf05064a18121b8e55fe1a4f19bc1d7cb9a142f4add54bc336e", + "blk.30.ffn_up_exps.weight": "12282f664a2a12aa25e2deac58946108715ebb978bafed5274cef24569107646", + "blk.30.attn_norm.weight": "1a33458fee054c6c9c896a4bb0a4e1fbfa0293b2408c7dd2b81d692e966e7273", + "blk.30.ffn_norm.weight": "311e33b68051f507f1478ed8f2693fddb846170ddb7285a91be43f795c2ce31e", + "blk.31.ffn_gate_exps.weight": "8af43d9867a51cd8392fb48b981b0ceee0ae979c491c07d711b3b56b5162c786", + "blk.31.ffn_down_exps.weight": "5579cb7758c1600b19d1f540deffe081b575962e37437b3b2efb2fb0a2924e40", + "blk.31.ffn_up_exps.weight": "f2e7c005276b3a001fb40753f027fa10b4d5a346f43cf4b4bbdeec6e74e1cf6a", + "blk.31.ffn_gate_inp.weight": "89885dc0e30b6b16a90c0331d7fa3174671e941364e8102d934f02132237e61b", + "blk.31.attn_norm.weight": "99e4e9bf86a9edf8c404153a7e8a82324ba79da462622196e2faba161bd95172", + "blk.31.ffn_norm.weight": "55335997cf6de781bf332b943de96ff4646966b05d9fee86b76ea897e27b6ca7", + "blk.31.attn_k.weight": "cee570762b78da6316b637892cc4b080e40f57af5551ffb1866b9a8e80e96628", + "blk.31.attn_output.weight": "fa321ff55ec7819ead7b819fd45215262f39744569765ba2113c989c03588802", + "blk.31.attn_q.weight": "9e2c409b878f8a2a1436874abf428fceb1c534b21f9ad4dd6f532b8a469007f0", + "blk.31.attn_v.weight": "a845d0be68ba537b4a775bfba4d897faf7c82a811a2612b0b7420cc4f3574cb8", + "output.weight": "16101cbb74b54cda9ebc07ca3c762e3263a56efb3cc011156184b95807d7cf13", + "output_norm.weight": "d7aa61585baedd60157aafe157930785742c55989c288573566a971b02423564" +} diff --git a/convert/tokenizer.go b/convert/tokenizer.go index fd6df5f53..baee04aa7 100644 --- a/convert/tokenizer.go +++ b/convert/tokenizer.go @@ -3,19 +3,148 @@ package convert import ( "cmp" "crypto/sha256" + "encoding/hex" "encoding/json" + "errors" "fmt" "log/slog" "os" + "path/filepath" "slices" +) - "golang.org/x/exp/maps" +const ( + _ int32 = iota + tokenTypeNormal + tokenTypeUnknown + tokenTypeControl + tokenTypeUserDefined + tokenTypeUnused + tokenTypeByte ) type Tokenizer struct { - Version string `json:"version"` - AddedTokens []Token `json:"added_tokens"` - Model TokenizerModel `json:"model"` + *Vocabulary + SpecialVocabulary []*SpecialVocabulary + Merges []string + + Pre string + Template string +} + +func parseTokenizer(d string, specialTypes []string) (*Tokenizer, error) { + v, err := parseVocabulary(d) + if err != nil { + return nil, err + } + + t := &Tokenizer{ + Vocabulary: v, + Pre: "default", + } + + addedTokens := make(map[string]token) + if f, err := os.Open(filepath.Join(d, "tokenizer.json")); errors.Is(err, os.ErrNotExist) { + } else if err != nil { + return nil, err + } else { + defer f.Close() + + var tt tokenizer + if err := json.NewDecoder(f).Decode(&tt); err != nil { + return nil, err + } + + for _, t := range tt.AddedTokens { + addedTokens[t.Content] = t + } + + t.Merges = tt.Model.Merges + + sha256sum := sha256.New() + for _, pt := range tt.PreTokenizer.PreTokenizers { + switch pt.Type { + case "Split": + if pt.Pattern.Regex != "" { + sha256sum.Write([]byte(pt.Pattern.Regex)) + } + } + } + + switch digest := hex.EncodeToString(sha256sum.Sum(nil)); digest { + case "d98f9631be1e9607a9848c26c1f9eac1aa9fc21ac6ba82a2fc0741af9780a48f": + t.Pre = "llama-bpe" + case "03df5c5863ad70781dcfdef491ead25140f895fe8010964be0daefe27be32b02": + t.Pre = "deepseek-llm" + case "21cde974d587f0d54dc8d56b183cc1e6239600172035c68fbd6d4b9f8da0576e": + t.Pre = "deepseek-coder" + case "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855": + // noop, empty pretokenizer + default: + slog.Warn("unknown pretokenizer, using default", "digest", digest) + } + } + + if f, err := os.Open(filepath.Join(d, "tokenizer_config.json")); errors.Is(err, os.ErrNotExist) { + } else if err != nil { + return nil, err + } else { + defer f.Close() + + var p map[string]json.RawMessage + if err := json.NewDecoder(f).Decode(&p); err != nil { + return nil, err + } + + if template, ok := p["chat_template"]; ok { + if err := json.Unmarshal(template, &t.Template); err != nil { + return nil, err + } + } + + for _, st := range specialTypes { + sv := SpecialVocabulary{Type: st} + if bts, ok := p[fmt.Sprintf("add_%s_token", st)]; ok { + if err := json.Unmarshal(bts, &sv.AddToken); err != nil { + return nil, err + } + } + + if bts, ok := p[fmt.Sprintf("%s_token", st)]; ok { + var content string + if err := json.Unmarshal(bts, &content); err != nil { + var mm map[string]any + if err := json.Unmarshal(bts, &mm); err != nil { + continue + } + + content, ok = mm["content"].(string) + if !ok { + continue + } + } + + sv.Content = content + } + + if id, ok := addedTokens[sv.Content]; ok { + sv.ID = id.ID + t.SpecialVocabulary = append(t.SpecialVocabulary, &sv) + } + } + } + + return t, nil +} + +type tokenizer struct { + Version string `json:"version"` + AddedTokens []token `json:"added_tokens"` + Model struct { + Type string `json:"type"` + Vocab map[string]int `json:"vocab"` + Merges []string `json:"merges"` + } `json:"model"` PreTokenizer struct { PreTokenizers []struct { @@ -27,80 +156,106 @@ type Tokenizer struct { } `json:"pre_tokenizer"` } -type TokenizerModel struct { - Type string `json:"type"` - Vocab map[string]int `json:"vocab"` - Merges []string `json:"merges"` - Tokens []Token -} - -type Token struct { +type token struct { ID int `json:"id"` Content string `json:"content"` Special bool `json:"special"` UserDefined bool } -func (t *Token) Type() int32 { - switch { - case t.Special: - return tokenTypeControl - case t.UserDefined: - return tokenTypeUserDefined - default: - return tokenTypeNormal - } +type Vocabulary struct { + Model string + Tokens []string + Scores []float32 + Types []int32 } -func (t *Tokenizer) maxID() int { - return max( - slices.Max(maps.Values(t.Model.Vocab)), - slices.MaxFunc(t.AddedTokens, func(a, b Token) int { - return cmp.Compare(a.ID, b.ID) - }).ID, - ) -} - -func parseTokens(dirpath string) (pre string, tokens []Token, merges []string, err error) { - f, err := os.Open(dirpath) +func parseVocabularyFromTokenizer(p string) (*Vocabulary, error) { + f, err := os.Open(filepath.Join(p, "tokenizer.json")) if err != nil { - panic(err) + return nil, err } defer f.Close() - var t Tokenizer + var t tokenizer if err := json.NewDecoder(f).Decode(&t); err != nil { - return "", nil, nil, err + return nil, err } - tokens = make([]Token, t.maxID()+1) + var tokens []token for k, v := range t.Model.Vocab { - tokens[v] = Token{ID: v, Content: k, Special: false, UserDefined: false} + tokens = append(tokens, token{ + ID: v, + Content: k, + }) } - for _, v := range t.AddedTokens { - v.UserDefined = true - tokens[v.ID] = v + for _, t := range t.AddedTokens { + t.UserDefined = true + tokens = append(tokens, t) } - sha256sum := sha256.New() - for _, pt := range t.PreTokenizer.PreTokenizers { - if pt.Type == "Split" && pt.Pattern.Regex != "" { - sha256sum.Write([]byte(pt.Pattern.Regex)) + slices.SortFunc(tokens, func(i, j token) int { + return cmp.Compare(i.ID, j.ID) + }) + + v := Vocabulary{Model: "gpt2"} + for _, t := range tokens { + v.Tokens = append(v.Tokens, t.Content) + v.Scores = append(v.Scores, float32(t.ID)) + + switch { + case t.Special: + v.Types = append(v.Types, tokenTypeControl) + case t.UserDefined: + v.Types = append(v.Types, tokenTypeUserDefined) + default: + v.Types = append(v.Types, tokenTypeNormal) } } - switch digest := fmt.Sprintf("%x", sha256sum.Sum(nil)); digest { - case "d98f9631be1e9607a9848c26c1f9eac1aa9fc21ac6ba82a2fc0741af9780a48f": - pre = "llama-bpe" - case "03df5c5863ad70781dcfdef491ead25140f895fe8010964be0daefe27be32b02": - pre = "deepseek-llm" - case "21cde974d587f0d54dc8d56b183cc1e6239600172035c68fbd6d4b9f8da0576e": - pre = "deepseek-coder" - default: - slog.Warn("unknown pretokenizer, using default", "digest", digest) - pre = "default" + return &v, nil +} + +func parseVocabulary(d string) (*Vocabulary, error) { + patterns := map[string]func(string) (*Vocabulary, error){ + "tokenizer.model": parseSentencePiece, + "tokenizer.json": parseVocabularyFromTokenizer, } - return pre, tokens, t.Model.Merges, nil + for pattern, parseFn := range patterns { + matches, err := filepath.Glob(filepath.Join(d, pattern)) + if err != nil { + return nil, err + } + + if len(matches) > 0 { + return parseFn(d) + } + } + + return nil, errors.New("unknown tensor format") +} + +type SpecialVocabulary struct { + Type string + ID int + Content string + AddToken bool +} + +func (sv SpecialVocabulary) Key() string { + switch t := sv.Type; t { + case "bos", "eos", "cls", "mask": + return t + case "unk": + return "unknown" + case "sep": + //nolint:misspell // this is an upstream typo + return "seperator" + case "pad": + return "padding" + } + + panic("unknown special vocabulary type") } diff --git a/convert/tokenizer_spm.go b/convert/tokenizer_spm.go new file mode 100644 index 000000000..75d9fe267 --- /dev/null +++ b/convert/tokenizer_spm.go @@ -0,0 +1,83 @@ +package convert + +import ( + "cmp" + "encoding/json" + "errors" + "fmt" + "os" + "path/filepath" + "slices" + + "google.golang.org/protobuf/proto" + + "github.com/ollama/ollama/convert/sentencepiece" +) + +func parseSentencePiece(d string) (*Vocabulary, error) { + bts, err := os.ReadFile(filepath.Join(d, "tokenizer.model")) + if err != nil { + return nil, err + } + + var spm sentencepiece.ModelProto + if err := proto.Unmarshal(bts, &spm); err != nil { + return nil, err + } + + v := Vocabulary{Model: "llama"} + for _, piece := range spm.GetPieces() { + v.Tokens = append(v.Tokens, piece.GetPiece()) + v.Scores = append(v.Scores, piece.GetScore()) + + switch t := piece.GetType(); t { + case sentencepiece.ModelProto_SentencePiece_UNKNOWN, + sentencepiece.ModelProto_SentencePiece_CONTROL, + sentencepiece.ModelProto_SentencePiece_UNUSED, + sentencepiece.ModelProto_SentencePiece_BYTE: + v.Types = append(v.Types, int32(t)) + default: + v.Types = append(v.Types, int32(sentencepiece.ModelProto_SentencePiece_NORMAL)) + } + } + + f, err := os.Open(filepath.Join(d, "added_tokens.json")) + if errors.Is(err, os.ErrNotExist) { + return &v, nil + } else if err != nil { + return nil, err + } + defer f.Close() + + var atm map[string]int + if err := json.NewDecoder(f).Decode(&atm); err != nil { + return nil, err + } + + type t struct { + id int + content string + } + + var ts []t + for content, id := range atm { + ts = append(ts, t{id, content}) + } + + slices.SortFunc(ts, func(i, j t) int { + return cmp.Compare(i.id, j.id) + }) + + n := len(v.Tokens) + for i, t := range ts { + if t.id != i+n { + return nil, fmt.Errorf("invalid token id: %d", t.id) + } + + v.Tokens = append(v.Tokens, t.content) + v.Scores = append(v.Scores, -1000.0) + v.Types = append(v.Types, tokenTypeUserDefined) + } + + return &v, nil +} diff --git a/convert/torch.go b/convert/torch.go deleted file mode 100644 index 55414adc6..000000000 --- a/convert/torch.go +++ /dev/null @@ -1,287 +0,0 @@ -package convert - -import ( - "encoding/binary" - "encoding/json" - "fmt" - "io" - "log/slog" - "os" - "path/filepath" - "regexp" - "strings" - - "github.com/nlpodyssey/gopickle/pytorch" - "github.com/nlpodyssey/gopickle/types" - "github.com/x448/float16" - - "github.com/ollama/ollama/llm" -) - -type torchWriterTo struct { - t *llm.Tensor - - params *Params - bo ByteOrder - - storage pytorch.StorageInterface - repacker func(string, []float32, []uint64) ([]float32, error) -} - -type TorchFormat struct{} - -func (tf *TorchFormat) GetTensors(dirpath string, params *Params) ([]llm.Tensor, error) { - slog.Debug("getting torch tensors") - - var files []string - if pt, _ := filepath.Glob(filepath.Join(dirpath, "consolidated*.pth")); len(pt) > 0 { - files = append(files, pt...) - } else if pt, _ := filepath.Glob(filepath.Join(dirpath, "pytorch_model*.pth")); len(pt) > 0 { - files = append(files, pt...) - } - - var offset uint64 - var tensors []llm.Tensor - for _, fn := range files { - m, err := pytorch.Load(fn) - if err != nil { - slog.Error(fmt.Sprintf("error unpickling: %q", err)) - return []llm.Tensor{}, err - } - - for _, k := range m.(*types.Dict).Keys() { - if strings.HasSuffix(k.(string), "self_attn.rotary_emb.inv_freq") { - continue - } - - t, _ := m.(*types.Dict).Get(k) - tshape := t.(*pytorch.Tensor).Size - - var size uint64 - var kind uint32 - switch len(tshape) { - case 0: - continue - case 1: - // convert to float32 - kind = 0 - size = uint64(tshape[0] * 4) - case 2: - // convert to float16 - kind = 1 - size = uint64(tshape[0] * tshape[1] * 2) - } - - ggufName, err := tf.GetLayerName(k.(string)) - if err != nil { - slog.Error(err.Error()) - return nil, err - } - slog.Debug(fmt.Sprintf("'%35s': '%30s' %10d [%#v]", k.(string), ggufName, size, tshape)) - - shape := []uint64{0, 0, 0, 0} - for i := range tshape { - shape[i] = uint64(tshape[i]) - } - - tensor := llm.Tensor{ - Name: ggufName, - Kind: kind, - Offset: offset, // calculate the offset - Shape: shape, - } - - tensor.WriterTo = torchWriterTo{ - t: &tensor, - params: params, - bo: params.ByteOrder, - storage: t.(*pytorch.Tensor).Source, - } - - tensors = append(tensors, tensor) - offset += size - } - } - - return tensors, nil -} - -func getAltParams(dirpath string) (*Params, error) { - f, err := os.Open(filepath.Join(dirpath, "params.json")) - if err != nil { - slog.Error("no params.json") - return nil, err - } - defer f.Close() - - type TorchParams struct { - HiddenSize int `json:"dim"` - AttentionHeads int `json:"n_heads"` - KeyValHeads int `json:"n_kv_heads"` - HiddenLayers int `json:"n_layers"` - RopeTheta float64 `json:"rope_theta"` - NormEPS float64 `json:"norm_eps"` - } - - var tparams TorchParams - - d := json.NewDecoder(f) - err = d.Decode(&tparams) - if err != nil { - return nil, err - } - - params := &Params{ - Architectures: []string{"LlamaForCausalLM"}, - HiddenSize: tparams.HiddenSize, - AttentionHeads: tparams.AttentionHeads, - KeyValHeads: tparams.KeyValHeads, - HiddenLayers: tparams.HiddenLayers, - NormEPS: tparams.NormEPS, - } - - switch { - case tparams.RopeTheta == 1000000: - // Codellama - params.ContextSize = 16384 - case tparams.NormEPS == 1e-06: - // llama2 - slog.Debug("Found llama2 - setting context size to 4096") - params.ContextSize = 4096 - default: - params.ContextSize = 2048 - } - - params.ByteOrder = binary.LittleEndian - return params, nil -} - -func (m *TorchFormat) GetParams(dirpath string) (*Params, error) { - f, err := os.Open(filepath.Join(dirpath, "config.json")) - if err != nil { - if os.IsNotExist(err) { - // try params.json instead - return getAltParams(dirpath) - } else { - return nil, err - } - } - - var params Params - d := json.NewDecoder(f) - err = d.Decode(¶ms) - if err != nil { - return nil, err - } - - params.ByteOrder = binary.LittleEndian - return ¶ms, nil -} - -func (m *TorchFormat) GetLayerName(n string) (string, error) { - directMap := map[string]string{ - "tok_embeddings.weight": "token_embd.weight", - "output.weight": "output.weight", - "norm.weight": "output_norm.weight", - "rope.freqs": "rope_freqs.weight", - "model.embed_tokens.weight": "token_embd.weight", - "lm_head.weight": "output.weight", - "model.norm.weight": "output_norm.weight", - } - - lMap := map[string]string{ - "layers.(\\d+).attention_norm.weight": "blk.$1.attn_norm.weight", - "layers.(\\d+).attention_output_norm.weight": "blk.$1.attn_norm.weight", - "layers.(\\d+).feed_forward.w2.weight": "blk.$1.ffn_down.weight", - "layers.(\\d+).feed_forward.w1.weight": "blk.$1.ffn_gate.weight", - "layers.(\\d+).feed_forward.w3.weight": "blk.$1.ffn_up.weight", - "layers.(\\d+).ffn_norm.weight": "blk.$1.ffn_norm.weight", - "layers.(\\d+).attention.wk.weight": "blk.$1.attn_k.weight", - "layers.(\\d+).attention.wo.weight": "blk.$1.attn_output.weight", - "layers.(\\d+).attention.wq.weight": "blk.$1.attn_q.weight", - "layers.(\\d+).attention.wv.weight": "blk.$1.attn_v.weight", - "model.layers.(\\d+).input_layernorm.weight": "blk.$1.attn_norm.weight", - "model.layers.(\\d+).mlp.down_proj.weight": "blk.$1.ffn_down.weight", - "model.layers.(\\d+).mlp.gate_proj.weight": "blk.$1.ffn_gate.weight", - "model.layers.(\\d+).mlp.up_proj.weight": "blk.$1.ffn_up.weight", - "model.layers.(\\d+).post_attention_layernorm.weight": "blk.$1.ffn_norm.weight", - "model.layers.(\\d+).self_attn.k_proj.weight": "blk.$1.attn_k.weight", - "model.layers.(\\d+).self_attn.o_proj.weight": "blk.$1.attn_output.weight", - "model.layers.(\\d+).self_attn.q_proj.weight": "blk.$1.attn_q.weight", - "model.layers.(\\d+).self_attn.v_proj.weight": "blk.$1.attn_v.weight", - } - - v, ok := directMap[n] - if ok { - return v, nil - } - - // quick hack to rename the layers to gguf format - for k, v := range lMap { - re := regexp.MustCompile(k) - newName := re.ReplaceAllString(n, v) - if newName != n { - return newName, nil - } - } - - return "", fmt.Errorf("couldn't find a layer name for '%s'", n) -} - -func (r torchWriterTo) WriteTo(w io.Writer) (n int64, err error) { - var f32s []float32 - switch s := r.storage.(type) { - case *pytorch.FloatStorage: - f32s = s.Data - case *pytorch.HalfStorage: - f32s = s.Data - case *pytorch.BFloat16Storage: - f32s = s.Data - default: - return 0, fmt.Errorf("unknown data type: %T", s) - } - - if r.repacker != nil { - f32s, err = r.repacker(r.t.Name, f32s, r.t.Shape) - if err != nil { - return 0, err - } - } - - switch r.t.Kind { - case 0: - return 0, binary.Write(w, r.bo, f32s) - case 1: - f16s := make([]uint16, len(f32s)) - for i := range f32s { - f16s[i] = float16.Fromfloat32(f32s[i]).Bits() - } - - return 0, binary.Write(w, r.bo, f16s) - default: - return 0, fmt.Errorf("unknown storage type: %d", r.t.Kind) - } -} - -func (m *TorchFormat) GetModelArch(name, dirPath string, params *Params) (ModelArch, error) { - switch len(params.Architectures) { - case 0: - return nil, fmt.Errorf("No architecture specified to convert") - case 1: - switch params.Architectures[0] { - case "LlamaForCausalLM": - return &LlamaModel{ - ModelData{ - Name: name, - Path: dirPath, - Params: params, - Format: m, - }, - }, nil - default: - return nil, fmt.Errorf("Models based on '%s' are not yet supported", params.Architectures[0]) - } - } - - return nil, fmt.Errorf("Unknown error") -} diff --git a/llm/gguf.go b/llm/gguf.go index aadfc4ba8..e61babf2b 100644 --- a/llm/gguf.go +++ b/llm/gguf.go @@ -2,11 +2,16 @@ package llm import ( "bytes" + "cmp" "encoding/binary" "encoding/json" "fmt" "io" + "log/slog" + "slices" "strings" + + "golang.org/x/exp/maps" ) type containerGGUF struct { @@ -88,7 +93,7 @@ type gguf struct { kv KV tensors []*Tensor - parameters uint64 + parameters uint64 tensorOffset uint64 scratch [16 << 10]byte @@ -101,10 +106,6 @@ func newGGUF(container *containerGGUF) *gguf { } } -func NewGGUFV3(bo binary.ByteOrder) *gguf { - return newGGUF(&containerGGUF{ByteOrder: bo, Version: 3}) -} - func (llm *gguf) KV() KV { return llm.kv } @@ -203,7 +204,7 @@ func (llm *gguf) Decode(rs io.ReadSeeker) error { return fmt.Errorf("failed to read tensor dimensions: %w", err) } - shape := [4]uint64{1, 1, 1, 1} + shape := make([]uint64, dims) for i := 0; uint32(i) < dims; i++ { shape[i], err = readGGUF[uint64](llm, rs) if err != nil { @@ -245,7 +246,7 @@ func (llm *gguf) Decode(rs io.ReadSeeker) error { return err } - padding := llm.padding(offset, int64(alignment)) + padding := ggufPadding(offset, int64(alignment)) llm.tensorOffset = uint64(offset + padding) for _, tensor := range llm.tensors { @@ -254,7 +255,7 @@ func (llm *gguf) Decode(rs io.ReadSeeker) error { return fmt.Errorf("failed to get current offset: %w", err) } - padding := llm.padding(offset, int64(alignment)) + padding := ggufPadding(offset, int64(alignment)) if _, err := rs.Seek(padding, io.SeekCurrent); err != nil { return fmt.Errorf("failed to seek to init padding: %w", err) } @@ -273,12 +274,12 @@ func readGGUF[T any](llm *gguf, r io.Reader) (T, error) { return t, err } -func writeGGUF[V any](llm *gguf, w io.Writer, t uint32, v V) error { - if err := binary.Write(w, llm.ByteOrder, t); err != nil { +func writeGGUF[V any](w io.Writer, t uint32, v V) error { + if err := binary.Write(w, binary.LittleEndian, t); err != nil { return err } - return binary.Write(w, llm.ByteOrder, v) + return binary.Write(w, binary.LittleEndian, v) } func readGGUFV1String(llm *gguf, r io.Reader) (string, error) { @@ -342,12 +343,12 @@ func readGGUFString(llm *gguf, r io.Reader) (string, error) { return string(buf), nil } -func writeGGUFString(llm *gguf, w io.Writer, s string) error { - if err := binary.Write(w, llm.ByteOrder, ggufTypeString); err != nil { +func writeGGUFString(w io.Writer, s string) error { + if err := binary.Write(w, binary.LittleEndian, ggufTypeString); err != nil { return err } - if err := binary.Write(w, llm.ByteOrder, uint64(len(s))); err != nil { + if err := binary.Write(w, binary.LittleEndian, uint64(len(s))); err != nil { return err } @@ -488,21 +489,21 @@ func readGGUFArray(llm *gguf, r io.Reader) (*array, error) { return a, nil } -func writeGGUFArray[S ~[]E, E any](llm *gguf, w io.Writer, t uint32, s S) error { - if err := binary.Write(w, llm.ByteOrder, ggufTypeArray); err != nil { +func writeGGUFArray[S ~[]E, E any](w io.Writer, t uint32, s S) error { + if err := binary.Write(w, binary.LittleEndian, ggufTypeArray); err != nil { return err } - if err := binary.Write(w, llm.ByteOrder, t); err != nil { + if err := binary.Write(w, binary.LittleEndian, t); err != nil { return err } - if err := binary.Write(w, llm.ByteOrder, uint64(len(s))); err != nil { + if err := binary.Write(w, binary.LittleEndian, uint64(len(s))); err != nil { return err } for _, e := range s { - if err := binary.Write(w, llm.ByteOrder, e); err != nil { + if err := binary.Write(w, binary.LittleEndian, e); err != nil { return err } } @@ -510,194 +511,55 @@ func writeGGUFArray[S ~[]E, E any](llm *gguf, w io.Writer, t uint32, s S) error return nil } -var ggufKVOrder = map[string][]string{ - "llama": { - "general.architecture", - "general.name", - "llama.vocab_size", - "llama.context_length", - "llama.embedding_length", - "llama.block_count", - "llama.feed_forward_length", - "llama.attention.head_count", - "llama.attention.head_count_kv", - "llama.attention.layer_norm_rms_epsilon", - "llama.rope.freq_base", - "llama.rope.dimension_count", - "llama.expert_count", - "llama.expert_used_count", - "gemma.context_length", - "gemma.embedding_length", - "gemma.block_count", - "gemma.feed_forward_length", - "gemma.attention.head_count", - "gemma.attention.head_count_kv", - "gemma.attention.layer_norm_rms_epsilon", - "gemma.attention.key_length", - "gemma.attention.value_length", - "general.file_type", - "tokenizer.ggml.pre", - "tokenizer.ggml.model", - "tokenizer.ggml.tokens", - "tokenizer.ggml.scores", - "tokenizer.ggml.merges", - "tokenizer.ggml.token_type", - "tokenizer.ggml.bos_token_id", - "tokenizer.ggml.eos_token_id", - "tokenizer.ggml.unknown_token_id", - "tokenizer.ggml.padding_token_id", - "tokenizer.ggml.add_bos_token", - "tokenizer.ggml.add_eos_token", - "tokenizer.chat_template", - "bert.pooling_type", - }, -} - -func (llm *gguf) Encode(ws io.WriteSeeker, kv KV, tensors []Tensor) error { - switch llm.Version { - case 3: - llm.V3.NumTensor = uint64(len(tensors)) - llm.V3.NumKV = uint64(len(kv)) - default: - return fmt.Errorf("not implemented: ggufv%d", llm.Version) - } - - if err := binary.Write(ws, llm.ByteOrder, []byte("GGUF")); err != nil { +func WriteGGUF(ws io.WriteSeeker, kv KV, ts []*Tensor) error { + if err := binary.Write(ws, binary.LittleEndian, []byte("GGUF")); err != nil { return err } - if err := binary.Write(ws, llm.ByteOrder, llm.Version); err != nil { + if err := binary.Write(ws, binary.LittleEndian, uint32(3)); err != nil { return err } - if err := binary.Write(ws, llm.ByteOrder, llm.numTensor()); err != nil { + if err := binary.Write(ws, binary.LittleEndian, uint64(len(ts))); err != nil { return err } - if err := binary.Write(ws, llm.ByteOrder, llm.numKV()); err != nil { + if err := binary.Write(ws, binary.LittleEndian, uint64(len(kv))); err != nil { return err } - kvCheck := make(map[string]bool) - for k := range kv { - kvCheck[k] = false - } + keys := maps.Keys(kv) + slices.Sort(keys) - for _, k := range ggufKVOrder["llama"] { - v, ok := kv[k] - if !ok { - continue - } - kvCheck[k] = true - - if err := binary.Write(ws, llm.ByteOrder, uint64(len(k))); err != nil { - return err - } - - if err := binary.Write(ws, llm.ByteOrder, []byte(k)); err != nil { - return err - } - - var err error - switch v := v.(type) { - case uint32: - err = writeGGUF(llm, ws, ggufTypeUint32, v) - case float32: - err = writeGGUF(llm, ws, ggufTypeFloat32, v) - case bool: - err = writeGGUF(llm, ws, ggufTypeBool, v) - case string: - err = writeGGUFString(llm, ws, v) - case []int32: - err = writeGGUFArray(llm, ws, ggufTypeInt32, v) - case []uint32: - err = writeGGUFArray(llm, ws, ggufTypeUint32, v) - case []float32: - err = writeGGUFArray(llm, ws, ggufTypeFloat32, v) - case []string: - if err := binary.Write(ws, llm.ByteOrder, ggufTypeArray); err != nil { - return err - } - - if err := binary.Write(ws, llm.ByteOrder, ggufTypeString); err != nil { - return err - } - - if err := binary.Write(ws, llm.ByteOrder, uint64(len(v))); err != nil { - return err - } - - for _, e := range v { - if err := binary.Write(ws, llm.ByteOrder, uint64(len(e))); err != nil { - return err - } - - if err := binary.Write(ws, llm.ByteOrder, []byte(e)); err != nil { - return err - } - } - default: - return fmt.Errorf("improper type for '%s'", k) - } - if err != nil { + for _, key := range keys { + if err := ggufWriteKV(ws, key, kv[key]); err != nil { return err } } - for k, v := range kvCheck { - if !v { - return fmt.Errorf("Didn't know how to write kv %s", k) + slices.SortFunc(ts, func(a, b *Tensor) int { + var i, j int + if n, err := fmt.Sscanf(a.Name, "blk.%d", &i); err != nil || n != 1 { + return cmp.Compare(a.Name, b.Name) + } else if n, err := fmt.Sscanf(b.Name, "blk.%d", &j); err != nil || n != 1 { + return cmp.Compare(a.Name, b.Name) } - } - for _, tensor := range tensors { - if err := binary.Write(ws, llm.ByteOrder, uint64(len(tensor.Name))); err != nil { - return err - } - - if err := binary.Write(ws, llm.ByteOrder, []byte(tensor.Name)); err != nil { - return err - } - - var dims int - for cnt := range len(tensor.Shape) { - if tensor.Shape[cnt] > 0 { - dims++ - } - } - - if err := binary.Write(ws, llm.ByteOrder, uint32(dims)); err != nil { - return err - } - - for i := range dims { - if err := binary.Write(ws, llm.ByteOrder, tensor.Shape[dims-1-i]); err != nil { - return err - } - } - - if err := binary.Write(ws, llm.ByteOrder, tensor.Kind); err != nil { - return err - } - - if err := binary.Write(ws, llm.ByteOrder, tensor.Offset); err != nil { + return cmp.Compare(i, j) + }) + + var s uint64 + for _, t := range ts { + t.Offset = s + if err := ggufWriteTensorInfo(ws, t); err != nil { return err } + s += t.Size() } var alignment int64 = 32 - for _, tensor := range tensors { - offset, err := ws.Seek(0, io.SeekCurrent) - if err != nil { - return err - } - - padding := llm.padding(offset, alignment) - if err := binary.Write(ws, llm.ByteOrder, bytes.Repeat([]byte{0}, int(padding))); err != nil { - return err - } - - if _, err := tensor.WriteTo(ws); err != nil { + for _, t := range ts { + if err := ggufWriteTensor(ws, t, alignment); err != nil { return err } } @@ -705,6 +567,102 @@ func (llm *gguf) Encode(ws io.WriteSeeker, kv KV, tensors []Tensor) error { return nil } -func (gguf) padding(offset, align int64) int64 { +func ggufWriteKV(ws io.WriteSeeker, k string, v any) error { + slog.Debug(k, "type", fmt.Sprintf("%T", v)) + if err := binary.Write(ws, binary.LittleEndian, uint64(len(k))); err != nil { + return err + } + + if err := binary.Write(ws, binary.LittleEndian, []byte(k)); err != nil { + return err + } + + var err error + switch v := v.(type) { + case uint32: + err = writeGGUF(ws, ggufTypeUint32, v) + case float32: + err = writeGGUF(ws, ggufTypeFloat32, v) + case bool: + err = writeGGUF(ws, ggufTypeBool, v) + case string: + err = writeGGUFString(ws, v) + case []int32: + err = writeGGUFArray(ws, ggufTypeInt32, v) + case []uint32: + err = writeGGUFArray(ws, ggufTypeUint32, v) + case []float32: + err = writeGGUFArray(ws, ggufTypeFloat32, v) + case []string: + if err := binary.Write(ws, binary.LittleEndian, ggufTypeArray); err != nil { + return err + } + + if err := binary.Write(ws, binary.LittleEndian, ggufTypeString); err != nil { + return err + } + + if err := binary.Write(ws, binary.LittleEndian, uint64(len(v))); err != nil { + return err + } + + for _, e := range v { + if err := binary.Write(ws, binary.LittleEndian, uint64(len(e))); err != nil { + return err + } + + if err := binary.Write(ws, binary.LittleEndian, []byte(e)); err != nil { + return err + } + } + default: + return fmt.Errorf("improper type for '%s'", k) + } + + return err +} + +func ggufWriteTensorInfo(ws io.WriteSeeker, t *Tensor) error { + slog.Debug(t.Name, "kind", t.Kind, "shape", t.Shape, "offset", t.Offset) + if err := binary.Write(ws, binary.LittleEndian, uint64(len(t.Name))); err != nil { + return err + } + + if err := binary.Write(ws, binary.LittleEndian, []byte(t.Name)); err != nil { + return err + } + + if err := binary.Write(ws, binary.LittleEndian, uint32(len(t.Shape))); err != nil { + return err + } + + for i := range len(t.Shape) { + if err := binary.Write(ws, binary.LittleEndian, t.Shape[len(t.Shape)-i-1]); err != nil { + return err + } + } + + if err := binary.Write(ws, binary.LittleEndian, t.Kind); err != nil { + return err + } + + return binary.Write(ws, binary.LittleEndian, t.Offset) +} + +func ggufWriteTensor(ws io.WriteSeeker, t *Tensor, alignment int64) error { + offset, err := ws.Seek(0, io.SeekCurrent) + if err != nil { + return err + } + + if err := binary.Write(ws, binary.LittleEndian, bytes.Repeat([]byte{0}, int(ggufPadding(offset, alignment)))); err != nil { + return err + } + + _, err = t.WriteTo(ws) + return err +} + +func ggufPadding(offset, align int64) int64 { return (align - offset%align) % align } diff --git a/llm/memory_test.go b/llm/memory_test.go index 06ae74387..18c797ee2 100644 --- a/llm/memory_test.go +++ b/llm/memory_test.go @@ -2,7 +2,6 @@ package llm import ( "bytes" - "encoding/binary" "fmt" "os" "testing" @@ -20,10 +19,9 @@ func TestEstimateGPULayers(t *testing.T) { f, err := os.CreateTemp(t.TempDir(), modelName) require.NoError(t, err) defer f.Close() - gguf := NewGGUFV3(binary.LittleEndian) inputLayerCount := 5 - tensors := []Tensor{ + tensors := []*Tensor{ {Name: "blk.0.attn.weight", Kind: uint32(0), Offset: uint64(0), Shape: []uint64{1, 1, 1, 1}, WriterTo: bytes.NewReader(make([]byte, 32))}, {Name: "blk.1.attn.weight", Kind: uint32(0), Offset: uint64(0), Shape: []uint64{1, 1, 1, 1}, WriterTo: bytes.NewReader(make([]byte, 32))}, {Name: "blk.2.attn.weight", Kind: uint32(0), Offset: uint64(0), Shape: []uint64{1, 1, 1, 1}, WriterTo: bytes.NewReader(make([]byte, 32))}, @@ -32,7 +30,7 @@ func TestEstimateGPULayers(t *testing.T) { {Name: "output.weight", Kind: uint32(0), Offset: uint64(0), Shape: []uint64{1, 1, 1, 1}, WriterTo: bytes.NewReader(make([]byte, 32))}, } assert.Len(t, tensors, inputLayerCount+1) - err = gguf.Encode(f, KV{ + err = WriteGGUF(f, KV{ "general.architecture": "llama", "general.name": "name", "llama.context_length": uint32(32), diff --git a/server/model.go b/server/model.go index c6d3078f1..81272a34c 100644 --- a/server/model.go +++ b/server/model.go @@ -143,30 +143,6 @@ func parseFromZipFile(_ context.Context, file *os.File, digest string, fn func(a return nil, err } - mf, err := convert.GetModelFormat(tempDir) - if err != nil { - return nil, err - } - - params, err := mf.GetParams(tempDir) - if err != nil { - return nil, err - } - - mArch, err := mf.GetModelArch("", tempDir, params) - if err != nil { - return nil, err - } - - fn(api.ProgressResponse{Status: "processing tensors"}) - if err := mArch.GetTensors(); err != nil { - return nil, err - } - - if err := mArch.LoadVocab(); err != nil { - return nil, err - } - fn(api.ProgressResponse{Status: "converting model"}) // TODO(mxyng): this should write directly into a layer @@ -178,7 +154,7 @@ func parseFromZipFile(_ context.Context, file *os.File, digest string, fn func(a defer temp.Close() defer os.Remove(temp.Name()) - if err = mArch.WriteGGUF(temp); err != nil { + if err := convert.Convert(tempDir, temp); err != nil { return nil, err } diff --git a/server/routes_create_test.go b/server/routes_create_test.go index 8c7142094..4d616d8de 100644 --- a/server/routes_create_test.go +++ b/server/routes_create_test.go @@ -2,7 +2,6 @@ package server import ( "bytes" - "encoding/binary" "encoding/json" "fmt" "io" @@ -20,7 +19,7 @@ import ( var stream bool = false -func createBinFile(t *testing.T, kv map[string]any, ti []llm.Tensor) string { +func createBinFile(t *testing.T, kv map[string]any, ti []*llm.Tensor) string { t.Helper() f, err := os.CreateTemp(t.TempDir(), "") @@ -29,7 +28,7 @@ func createBinFile(t *testing.T, kv map[string]any, ti []llm.Tensor) string { } defer f.Close() - if err := llm.NewGGUFV3(binary.LittleEndian).Encode(f, kv, ti); err != nil { + if err := llm.WriteGGUF(f, kv, ti); err != nil { t.Fatal(err) } diff --git a/server/routes_generate_test.go b/server/routes_generate_test.go index 5c0caff1c..02f95dd28 100644 --- a/server/routes_generate_test.go +++ b/server/routes_generate_test.go @@ -101,7 +101,7 @@ func TestGenerateChat(t *testing.T) { "tokenizer.ggml.tokens": []string{""}, "tokenizer.ggml.scores": []float32{0}, "tokenizer.ggml.token_type": []int32{0}, - }, []llm.Tensor{ + }, []*llm.Tensor{ {Name: "token_embd.weight", Shape: []uint64{1}, WriterTo: bytes.NewReader(make([]byte, 4))}, {Name: "blk.0.attn_norm.weight", Shape: []uint64{1}, WriterTo: bytes.NewReader(make([]byte, 4))}, {Name: "blk.0.ffn_down.weight", Shape: []uint64{1}, WriterTo: bytes.NewReader(make([]byte, 4))}, @@ -149,7 +149,7 @@ func TestGenerateChat(t *testing.T) { Modelfile: fmt.Sprintf("FROM %s", createBinFile(t, llm.KV{ "general.architecture": "bert", "bert.pooling_type": uint32(0), - }, []llm.Tensor{})), + }, []*llm.Tensor{})), Stream: &stream, }) @@ -399,7 +399,7 @@ func TestGenerate(t *testing.T) { "tokenizer.ggml.tokens": []string{""}, "tokenizer.ggml.scores": []float32{0}, "tokenizer.ggml.token_type": []int32{0}, - }, []llm.Tensor{ + }, []*llm.Tensor{ {Name: "token_embd.weight", Shape: []uint64{1}, WriterTo: bytes.NewReader(make([]byte, 4))}, {Name: "blk.0.attn_norm.weight", Shape: []uint64{1}, WriterTo: bytes.NewReader(make([]byte, 4))}, {Name: "blk.0.ffn_down.weight", Shape: []uint64{1}, WriterTo: bytes.NewReader(make([]byte, 4))}, @@ -447,7 +447,7 @@ func TestGenerate(t *testing.T) { Modelfile: fmt.Sprintf("FROM %s", createBinFile(t, llm.KV{ "general.architecture": "bert", "bert.pooling_type": uint32(0), - }, []llm.Tensor{})), + }, []*llm.Tensor{})), Stream: &stream, }) diff --git a/server/sched_test.go b/server/sched_test.go index 6959dace3..f3c555147 100644 --- a/server/sched_test.go +++ b/server/sched_test.go @@ -3,7 +3,6 @@ package server import ( "bytes" "context" - "encoding/binary" "fmt" "log/slog" "os" @@ -114,8 +113,7 @@ func newScenarioRequest(t *testing.T, ctx context.Context, modelName string, est require.NoError(t, err) defer f.Close() - gguf := llm.NewGGUFV3(binary.LittleEndian) - err = gguf.Encode(f, llm.KV{ + require.NoError(t, llm.WriteGGUF(f, llm.KV{ "general.architecture": "llama", "general.name": "name", "llama.context_length": uint32(32), @@ -126,10 +124,10 @@ func newScenarioRequest(t *testing.T, ctx context.Context, modelName string, est "tokenizer.ggml.tokens": []string{" "}, "tokenizer.ggml.scores": []float32{0}, "tokenizer.ggml.token_type": []int32{0}, - }, []llm.Tensor{ + }, []*llm.Tensor{ {Name: "blk.0.attn.weight", Kind: uint32(0), Offset: uint64(0), Shape: []uint64{1, 1, 1, 1}, WriterTo: bytes.NewReader(make([]byte, 32))}, {Name: "output.weight", Kind: uint32(0), Offset: uint64(0), Shape: []uint64{1, 1, 1, 1}, WriterTo: bytes.NewReader(make([]byte, 32))}, - }) + })) require.NoError(t, err) fname := f.Name() From df993fa37bde19039231001be9f852386a12a860 Mon Sep 17 00:00:00 2001 From: Michael Yang Date: Mon, 8 Jul 2024 16:59:48 -0700 Subject: [PATCH 181/384] comments --- convert/convert.go | 46 +++++++++++++++++----------------- convert/convert_gemma.go | 6 ++--- convert/convert_llama.go | 6 ++--- convert/convert_mixtral.go | 6 ++--- convert/reader.go | 9 +++++-- convert/reader_safetensors.go | 5 ++-- convert/tokenizer.go | 15 +++++------ llm/gguf.go | 17 +++++-------- llm/memory_test.go | 2 +- server/routes_create_test.go | 2 +- server/routes_generate_test.go | 8 +++--- server/sched_test.go | 2 +- 12 files changed, 63 insertions(+), 61 deletions(-) diff --git a/convert/convert.go b/convert/convert.go index 4ad64d721..30c5a53f8 100644 --- a/convert/convert.go +++ b/convert/convert.go @@ -40,13 +40,13 @@ func (Parameters) KV(t *Tokenizer) llm.KV { return kv } -func (Parameters) specialTypes() []string { +func (Parameters) specialTokenTypes() []string { return []string{ "bos", "eos", "unk", "sep", "pad", "cls", "mask", } } -func (Parameters) writeFile(ws io.WriteSeeker, kv llm.KV, ts []*llm.Tensor) error { +func (Parameters) writeFile(ws io.WriteSeeker, kv llm.KV, ts []llm.Tensor) error { return llm.WriteGGUF(ws, kv, ts) } @@ -54,24 +54,27 @@ type Converter interface { // KV maps parameters to LLM key-values KV(*Tokenizer) llm.KV // Tensors maps input tensors to LLM tensors. Model specific modifications can be done here. - Tensors([]Tensor) []*llm.Tensor + Tensors([]Tensor) []llm.Tensor // tensorName returns the LLM tensor name for a specific input name tensorName(string) string - // specialTypes returns any special token types the model uses - specialTypes() []string - writeFile(io.WriteSeeker, llm.KV, []*llm.Tensor) error + // specialTokenTypes returns any special token types the model uses + specialTokenTypes() []string + writeFile(io.WriteSeeker, llm.KV, []llm.Tensor) error } -func Convert(d string, ws io.WriteSeeker) error { - f, err := os.Open(filepath.Join(d, "config.json")) +// Convert writes an Ollama compatible model to the provided io.WriteSeeker based on configurations +// and files it finds in the input path. +// Supported input model formats include safetensors. +// Supported input tokenizers files include tokenizer.json (preferred) and tokenizer.model. +func Convert(path string, ws io.WriteSeeker) error { + bts, err := os.ReadFile(filepath.Join(path, "config.json")) if err != nil { return err } - defer f.Close() var p Parameters - if err := json.NewDecoder(f).Decode(&p); err != nil { + if err := json.Unmarshal(bts, &p); err != nil { return err } @@ -79,28 +82,23 @@ func Convert(d string, ws io.WriteSeeker) error { return errors.New("unknown architecture") } - var c Converter + var conv Converter switch p.Architectures[0] { case "LlamaForCausalLM", "MistralForCausalLM": - c = &llama{} + conv = &llama{} case "MixtralForCausalLM": - c = &mixtral{} + conv = &mixtral{} case "GemmaForCausalLM": - c = &gemma{} + conv = &gemma{} default: return errors.New("unsupported architecture") } - bts, err := os.ReadFile(filepath.Join(d, "config.json")) - if err != nil { + if err := json.Unmarshal(bts, conv); err != nil { return err } - if err := json.Unmarshal(bts, c); err != nil { - return err - } - - t, err := parseTokenizer(d, c.specialTypes()) + t, err := parseTokenizer(path, conv.specialTokenTypes()) if err != nil { return err } @@ -112,12 +110,14 @@ func Convert(d string, ws io.WriteSeeker) error { t.Vocabulary.Scores = append(t.Vocabulary.Scores, -1) t.Vocabulary.Types = append(t.Vocabulary.Types, tokenTypeUserDefined) } + } else { + slog.Debug("vocabulary", "size", len(t.Vocabulary.Tokens)) } - ts, err := parseTensors(d) + ts, err := parseTensors(path) if err != nil { return err } - return c.writeFile(ws, c.KV(t), c.Tensors(ts)) + return conv.writeFile(ws, conv.KV(t), conv.Tensors(ts)) } diff --git a/convert/convert_gemma.go b/convert/convert_gemma.go index 332fee7f2..9213e1576 100644 --- a/convert/convert_gemma.go +++ b/convert/convert_gemma.go @@ -43,15 +43,15 @@ func (p *gemma) KV(t *Tokenizer) llm.KV { return kv } -func (p *gemma) Tensors(ts []Tensor) []*llm.Tensor { - var out []*llm.Tensor +func (p *gemma) Tensors(ts []Tensor) []llm.Tensor { + var out []llm.Tensor for _, t := range ts { name := p.tensorName(t.Name()) if strings.HasSuffix(name, "_norm.weight") { t.SetRepacker(p.addOne) } - out = append(out, &llm.Tensor{ + out = append(out, llm.Tensor{ Name: name, Kind: t.Kind(), Shape: t.Shape(), diff --git a/convert/convert_llama.go b/convert/convert_llama.go index 700049d32..ed6469c5e 100644 --- a/convert/convert_llama.go +++ b/convert/convert_llama.go @@ -96,8 +96,8 @@ func (p *llama) KV(t *Tokenizer) llm.KV { return kv } -func (p *llama) Tensors(ts []Tensor) []*llm.Tensor { - var out []*llm.Tensor +func (p *llama) Tensors(ts []Tensor) []llm.Tensor { + var out []llm.Tensor for _, t := range ts { name := p.tensorName(t.Name()) if strings.HasSuffix(name, "attn_q.weight") || @@ -105,7 +105,7 @@ func (p *llama) Tensors(ts []Tensor) []*llm.Tensor { t.SetRepacker(p.repack) } - out = append(out, &llm.Tensor{ + out = append(out, llm.Tensor{ Name: name, Kind: t.Kind(), Shape: t.Shape(), diff --git a/convert/convert_mixtral.go b/convert/convert_mixtral.go index c55a27f81..3263a27b3 100644 --- a/convert/convert_mixtral.go +++ b/convert/convert_mixtral.go @@ -31,7 +31,7 @@ func (p *mixtral) KV(t *Tokenizer) llm.KV { return kv } -func (p *mixtral) Tensors(ts []Tensor) []*llm.Tensor { +func (p *mixtral) Tensors(ts []Tensor) []llm.Tensor { oldnew := []string{ "model.layers", "blk", "w1", "ffn_gate_exps", @@ -58,10 +58,10 @@ func (p *mixtral) Tensors(ts []Tensor) []*llm.Tensor { return true }) - var out []*llm.Tensor + var out []llm.Tensor for n, e := range experts { // TODO(mxyng): sanity check experts - out = append(out, &llm.Tensor{ + out = append(out, llm.Tensor{ Name: n, Kind: e[0].Kind(), Shape: append([]uint64{uint64(len(e))}, e[0].Shape()...), diff --git a/convert/reader.go b/convert/reader.go index 9be8ac2eb..11ccaa819 100644 --- a/convert/reader.go +++ b/convert/reader.go @@ -29,6 +29,11 @@ func (t tensorBase) Shape() []uint64 { return t.shape } +const ( + tensorKindF32 uint32 = iota + tensorKindF16 +) + func (t tensorBase) Kind() uint32 { if strings.HasSuffix(t.name, ".block_sparse_moe.gate.weight") { return 0 @@ -38,9 +43,9 @@ func (t tensorBase) Kind() uint32 { case 0: panic("invalid tensor shape") case 1: - return 0 + return tensorKindF32 default: - return 1 + return tensorKindF16 } } diff --git a/convert/reader_safetensors.go b/convert/reader_safetensors.go index 440581af8..d43c59a5e 100644 --- a/convert/reader_safetensors.go +++ b/convert/reader_safetensors.go @@ -66,6 +66,7 @@ func parseSafetensors(ps ...string) ([]Tensor, error) { return ts, nil } +// safetensorsPad returns the padded size of the safetensors file given a length n and offset s func safetensorsPad(n, s int64) int64 { return 8 + n + s } @@ -125,9 +126,9 @@ func (st safetensor) WriteTo(w io.Writer) (int64, error) { } switch st.Kind() { - case 0: + case tensorKindF32: return 0, binary.Write(w, binary.LittleEndian, f32s) - case 1: + case tensorKindF16: f16s := make([]uint16, len(f32s)) for i := range f32s { f16s[i] = float16.Fromfloat32(f32s[i]).Bits() diff --git a/convert/tokenizer.go b/convert/tokenizer.go index baee04aa7..43d8c14ef 100644 --- a/convert/tokenizer.go +++ b/convert/tokenizer.go @@ -32,7 +32,7 @@ type Tokenizer struct { Template string } -func parseTokenizer(d string, specialTypes []string) (*Tokenizer, error) { +func parseTokenizer(d string, specialTokenTypes []string) (*Tokenizer, error) { v, err := parseVocabulary(d) if err != nil { return nil, err @@ -66,6 +66,8 @@ func parseTokenizer(d string, specialTypes []string) (*Tokenizer, error) { switch pt.Type { case "Split": if pt.Pattern.Regex != "" { + // create a checksum of all Split pretokenizers which should be sufficient + // to identify the pretokenizer sha256sum.Write([]byte(pt.Pattern.Regex)) } } @@ -102,7 +104,7 @@ func parseTokenizer(d string, specialTypes []string) (*Tokenizer, error) { } } - for _, st := range specialTypes { + for _, st := range specialTokenTypes { sv := SpecialVocabulary{Type: st} if bts, ok := p[fmt.Sprintf("add_%s_token", st)]; ok { if err := json.Unmarshal(bts, &sv.AddToken); err != nil { @@ -224,14 +226,13 @@ func parseVocabulary(d string) (*Vocabulary, error) { } for pattern, parseFn := range patterns { - matches, err := filepath.Glob(filepath.Join(d, pattern)) - if err != nil { + if _, err := os.Stat(filepath.Join(d, pattern)); errors.Is(err, os.ErrNotExist) { + continue + } else if err != nil { return nil, err } - if len(matches) > 0 { - return parseFn(d) - } + return parseFn(d) } return nil, errors.New("unknown tensor format") diff --git a/llm/gguf.go b/llm/gguf.go index e61babf2b..981583131 100644 --- a/llm/gguf.go +++ b/llm/gguf.go @@ -489,6 +489,7 @@ func readGGUFArray(llm *gguf, r io.Reader) (*array, error) { return a, nil } +// writeGGUFArray writes a slice s of type E to the write with a gguf type of t func writeGGUFArray[S ~[]E, E any](w io.Writer, t uint32, s S) error { if err := binary.Write(w, binary.LittleEndian, ggufTypeArray); err != nil { return err @@ -502,16 +503,10 @@ func writeGGUFArray[S ~[]E, E any](w io.Writer, t uint32, s S) error { return err } - for _, e := range s { - if err := binary.Write(w, binary.LittleEndian, e); err != nil { - return err - } - } - - return nil + return binary.Write(w, binary.LittleEndian, s) } -func WriteGGUF(ws io.WriteSeeker, kv KV, ts []*Tensor) error { +func WriteGGUF(ws io.WriteSeeker, kv KV, ts []Tensor) error { if err := binary.Write(ws, binary.LittleEndian, []byte("GGUF")); err != nil { return err } @@ -537,7 +532,7 @@ func WriteGGUF(ws io.WriteSeeker, kv KV, ts []*Tensor) error { } } - slices.SortFunc(ts, func(a, b *Tensor) int { + slices.SortFunc(ts, func(a, b Tensor) int { var i, j int if n, err := fmt.Sscanf(a.Name, "blk.%d", &i); err != nil || n != 1 { return cmp.Compare(a.Name, b.Name) @@ -622,7 +617,7 @@ func ggufWriteKV(ws io.WriteSeeker, k string, v any) error { return err } -func ggufWriteTensorInfo(ws io.WriteSeeker, t *Tensor) error { +func ggufWriteTensorInfo(ws io.WriteSeeker, t Tensor) error { slog.Debug(t.Name, "kind", t.Kind, "shape", t.Shape, "offset", t.Offset) if err := binary.Write(ws, binary.LittleEndian, uint64(len(t.Name))); err != nil { return err @@ -649,7 +644,7 @@ func ggufWriteTensorInfo(ws io.WriteSeeker, t *Tensor) error { return binary.Write(ws, binary.LittleEndian, t.Offset) } -func ggufWriteTensor(ws io.WriteSeeker, t *Tensor, alignment int64) error { +func ggufWriteTensor(ws io.WriteSeeker, t Tensor, alignment int64) error { offset, err := ws.Seek(0, io.SeekCurrent) if err != nil { return err diff --git a/llm/memory_test.go b/llm/memory_test.go index 18c797ee2..3220c8df6 100644 --- a/llm/memory_test.go +++ b/llm/memory_test.go @@ -21,7 +21,7 @@ func TestEstimateGPULayers(t *testing.T) { defer f.Close() inputLayerCount := 5 - tensors := []*Tensor{ + tensors := []Tensor{ {Name: "blk.0.attn.weight", Kind: uint32(0), Offset: uint64(0), Shape: []uint64{1, 1, 1, 1}, WriterTo: bytes.NewReader(make([]byte, 32))}, {Name: "blk.1.attn.weight", Kind: uint32(0), Offset: uint64(0), Shape: []uint64{1, 1, 1, 1}, WriterTo: bytes.NewReader(make([]byte, 32))}, {Name: "blk.2.attn.weight", Kind: uint32(0), Offset: uint64(0), Shape: []uint64{1, 1, 1, 1}, WriterTo: bytes.NewReader(make([]byte, 32))}, diff --git a/server/routes_create_test.go b/server/routes_create_test.go index 4d616d8de..9b7009df9 100644 --- a/server/routes_create_test.go +++ b/server/routes_create_test.go @@ -19,7 +19,7 @@ import ( var stream bool = false -func createBinFile(t *testing.T, kv map[string]any, ti []*llm.Tensor) string { +func createBinFile(t *testing.T, kv map[string]any, ti []llm.Tensor) string { t.Helper() f, err := os.CreateTemp(t.TempDir(), "") diff --git a/server/routes_generate_test.go b/server/routes_generate_test.go index 02f95dd28..5c0caff1c 100644 --- a/server/routes_generate_test.go +++ b/server/routes_generate_test.go @@ -101,7 +101,7 @@ func TestGenerateChat(t *testing.T) { "tokenizer.ggml.tokens": []string{""}, "tokenizer.ggml.scores": []float32{0}, "tokenizer.ggml.token_type": []int32{0}, - }, []*llm.Tensor{ + }, []llm.Tensor{ {Name: "token_embd.weight", Shape: []uint64{1}, WriterTo: bytes.NewReader(make([]byte, 4))}, {Name: "blk.0.attn_norm.weight", Shape: []uint64{1}, WriterTo: bytes.NewReader(make([]byte, 4))}, {Name: "blk.0.ffn_down.weight", Shape: []uint64{1}, WriterTo: bytes.NewReader(make([]byte, 4))}, @@ -149,7 +149,7 @@ func TestGenerateChat(t *testing.T) { Modelfile: fmt.Sprintf("FROM %s", createBinFile(t, llm.KV{ "general.architecture": "bert", "bert.pooling_type": uint32(0), - }, []*llm.Tensor{})), + }, []llm.Tensor{})), Stream: &stream, }) @@ -399,7 +399,7 @@ func TestGenerate(t *testing.T) { "tokenizer.ggml.tokens": []string{""}, "tokenizer.ggml.scores": []float32{0}, "tokenizer.ggml.token_type": []int32{0}, - }, []*llm.Tensor{ + }, []llm.Tensor{ {Name: "token_embd.weight", Shape: []uint64{1}, WriterTo: bytes.NewReader(make([]byte, 4))}, {Name: "blk.0.attn_norm.weight", Shape: []uint64{1}, WriterTo: bytes.NewReader(make([]byte, 4))}, {Name: "blk.0.ffn_down.weight", Shape: []uint64{1}, WriterTo: bytes.NewReader(make([]byte, 4))}, @@ -447,7 +447,7 @@ func TestGenerate(t *testing.T) { Modelfile: fmt.Sprintf("FROM %s", createBinFile(t, llm.KV{ "general.architecture": "bert", "bert.pooling_type": uint32(0), - }, []*llm.Tensor{})), + }, []llm.Tensor{})), Stream: &stream, }) diff --git a/server/sched_test.go b/server/sched_test.go index f3c555147..80395714b 100644 --- a/server/sched_test.go +++ b/server/sched_test.go @@ -124,7 +124,7 @@ func newScenarioRequest(t *testing.T, ctx context.Context, modelName string, est "tokenizer.ggml.tokens": []string{" "}, "tokenizer.ggml.scores": []float32{0}, "tokenizer.ggml.token_type": []int32{0}, - }, []*llm.Tensor{ + }, []llm.Tensor{ {Name: "blk.0.attn.weight", Kind: uint32(0), Offset: uint64(0), Shape: []uint64{1, 1, 1, 1}, WriterTo: bytes.NewReader(make([]byte, 32))}, {Name: "output.weight", Kind: uint32(0), Offset: uint64(0), Shape: []uint64{1, 1, 1, 1}, WriterTo: bytes.NewReader(make([]byte, 32))}, })) From 781fc2d5769bd1df7895dc2a18ab44830f6684fc Mon Sep 17 00:00:00 2001 From: Michael Yang Date: Wed, 31 Jul 2024 10:58:22 -0700 Subject: [PATCH 182/384] Update convert/reader_safetensors.go Co-authored-by: Jeffrey Morgan --- convert/reader_safetensors.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/convert/reader_safetensors.go b/convert/reader_safetensors.go index d43c59a5e..c5fe663cc 100644 --- a/convert/reader_safetensors.go +++ b/convert/reader_safetensors.go @@ -67,8 +67,8 @@ func parseSafetensors(ps ...string) ([]Tensor, error) { } // safetensorsPad returns the padded size of the safetensors file given a length n and offset s -func safetensorsPad(n, s int64) int64 { - return 8 + n + s +func safetensorsPad(n, offset int64) int64 { + return 8 + n + offset } type safetensor struct { From eafc607abb3422a7d8e488aeb7a129a67a1f75c6 Mon Sep 17 00:00:00 2001 From: Michael Yang Date: Sat, 29 Jun 2024 16:53:59 -0700 Subject: [PATCH 183/384] convert: only extract large files --- convert/convert.go | 11 ++-- convert/convert_test.go | 7 ++- convert/fs.go | 58 +++++++++++++++++++ convert/reader.go | 10 ++-- convert/reader_safetensors.go | 20 +++++-- convert/reader_torch.go | 3 +- convert/tokenizer.go | 22 ++++---- convert/tokenizer_spm.go | 8 +-- server/model.go | 79 ++++++-------------------- server/model_test.go | 102 ---------------------------------- 10 files changed, 120 insertions(+), 200 deletions(-) create mode 100644 convert/fs.go diff --git a/convert/convert.go b/convert/convert.go index 30c5a53f8..b9461e4fe 100644 --- a/convert/convert.go +++ b/convert/convert.go @@ -5,9 +5,8 @@ import ( "errors" "fmt" "io" + "io/fs" "log/slog" - "os" - "path/filepath" "github.com/ollama/ollama/llm" ) @@ -67,8 +66,8 @@ type Converter interface { // and files it finds in the input path. // Supported input model formats include safetensors. // Supported input tokenizers files include tokenizer.json (preferred) and tokenizer.model. -func Convert(path string, ws io.WriteSeeker) error { - bts, err := os.ReadFile(filepath.Join(path, "config.json")) +func Convert(fsys fs.FS, ws io.WriteSeeker) error { + bts, err := fs.ReadFile(fsys, "config.json") if err != nil { return err } @@ -98,7 +97,7 @@ func Convert(path string, ws io.WriteSeeker) error { return err } - t, err := parseTokenizer(path, conv.specialTokenTypes()) + t, err := parseTokenizer(fsys, conv.specialTokenTypes()) if err != nil { return err } @@ -114,7 +113,7 @@ func Convert(path string, ws io.WriteSeeker) error { slog.Debug("vocabulary", "size", len(t.Vocabulary.Tokens)) } - ts, err := parseTensors(path) + ts, err := parseTensors(fsys) if err != nil { return err } diff --git a/convert/convert_test.go b/convert/convert_test.go index 0fbd436f5..67a2fcfe3 100644 --- a/convert/convert_test.go +++ b/convert/convert_test.go @@ -6,6 +6,7 @@ import ( "flag" "fmt" "io" + "io/fs" "log/slog" "math" "os" @@ -17,7 +18,7 @@ import ( "golang.org/x/exp/maps" ) -func convertFull(t *testing.T, d string) (*os.File, llm.KV, llm.Tensors) { +func convertFull(t *testing.T, fsys fs.FS) (*os.File, llm.KV, llm.Tensors) { t.Helper() f, err := os.CreateTemp(t.TempDir(), "f16") @@ -26,7 +27,7 @@ func convertFull(t *testing.T, d string) (*os.File, llm.KV, llm.Tensors) { } defer f.Close() - if err := Convert(d, f); err != nil { + if err := Convert(fsys, f); err != nil { t.Fatal(err) } @@ -76,7 +77,7 @@ func TestConvertFull(t *testing.T) { t.Skipf("%s not found", p) } - f, kv, tensors := convertFull(t, p) + f, kv, tensors := convertFull(t, os.DirFS(p)) actual := make(map[string]string) for k, v := range kv { if s, ok := v.(json.Marshaler); !ok { diff --git a/convert/fs.go b/convert/fs.go new file mode 100644 index 000000000..bf6da6c25 --- /dev/null +++ b/convert/fs.go @@ -0,0 +1,58 @@ +package convert + +import ( + "archive/zip" + "errors" + "io" + "io/fs" + "os" + "path/filepath" +) + +type ZipReader struct { + r *zip.Reader + p string + + // limit is the maximum size of a file that can be read directly + // from the zip archive. Files larger than this size will be extracted + limit int64 +} + +func NewZipReader(r *zip.Reader, p string, limit int64) fs.FS { + return &ZipReader{r, p, limit} +} + +func (z *ZipReader) Open(name string) (fs.File, error) { + r, err := z.r.Open(name) + if err != nil { + return nil, err + } + defer r.Close() + + if fi, err := r.Stat(); err != nil { + return nil, err + } else if fi.Size() < z.limit { + return r, nil + } + + if !filepath.IsLocal(name) { + return nil, zip.ErrInsecurePath + } + + n := filepath.Join(z.p, name) + if _, err := os.Stat(n); errors.Is(err, os.ErrNotExist) { + w, err := os.Create(n) + if err != nil { + return nil, err + } + defer w.Close() + + if _, err := io.Copy(w, r); err != nil { + return nil, err + } + } else if err != nil { + return nil, err + } + + return os.Open(n) +} diff --git a/convert/reader.go b/convert/reader.go index 11ccaa819..56a8ae895 100644 --- a/convert/reader.go +++ b/convert/reader.go @@ -3,7 +3,7 @@ package convert import ( "errors" "io" - "path/filepath" + "io/fs" "strings" ) @@ -55,8 +55,8 @@ func (t *tensorBase) SetRepacker(fn repacker) { type repacker func(string, []float32, []uint64) ([]float32, error) -func parseTensors(d string) ([]Tensor, error) { - patterns := map[string]func(...string) ([]Tensor, error){ +func parseTensors(fsys fs.FS) ([]Tensor, error) { + patterns := map[string]func(fs.FS, ...string) ([]Tensor, error){ "model-*-of-*.safetensors": parseSafetensors, "model.safetensors": parseSafetensors, "pytorch_model-*-of-*.bin": parseTorch, @@ -65,13 +65,13 @@ func parseTensors(d string) ([]Tensor, error) { } for pattern, parseFn := range patterns { - matches, err := filepath.Glob(filepath.Join(d, pattern)) + matches, err := fs.Glob(fsys, pattern) if err != nil { return nil, err } if len(matches) > 0 { - return parseFn(matches...) + return parseFn(fsys, matches...) } } diff --git a/convert/reader_safetensors.go b/convert/reader_safetensors.go index c5fe663cc..1c1695044 100644 --- a/convert/reader_safetensors.go +++ b/convert/reader_safetensors.go @@ -6,7 +6,7 @@ import ( "encoding/json" "fmt" "io" - "os" + "io/fs" "slices" "github.com/d4l3k/go-bfloat16" @@ -20,10 +20,10 @@ type safetensorMetadata struct { Offsets []int64 `json:"data_offsets"` } -func parseSafetensors(ps ...string) ([]Tensor, error) { +func parseSafetensors(fsys fs.FS, ps ...string) ([]Tensor, error) { var ts []Tensor for _, p := range ps { - f, err := os.Open(p) + f, err := fsys.Open(p) if err != nil { return nil, err } @@ -50,6 +50,7 @@ func parseSafetensors(ps ...string) ([]Tensor, error) { for _, key := range keys { if value := headers[key]; value.Type != "" { ts = append(ts, safetensor{ + fs: fsys, path: p, dtype: value.Type, offset: safetensorsPad(n, value.Offsets[0]), @@ -72,6 +73,7 @@ func safetensorsPad(n, offset int64) int64 { } type safetensor struct { + fs fs.FS path string dtype string offset int64 @@ -80,14 +82,20 @@ type safetensor struct { } func (st safetensor) WriteTo(w io.Writer) (int64, error) { - f, err := os.Open(st.path) + f, err := st.fs.Open(st.path) if err != nil { return 0, err } defer f.Close() - if _, err = f.Seek(st.offset, io.SeekStart); err != nil { - return 0, err + if seeker, ok := f.(io.Seeker); ok { + if _, err := seeker.Seek(st.offset, io.SeekStart); err != nil { + return 0, err + } + } else { + if _, err := io.CopyN(io.Discard, f, st.offset); err != nil { + return 0, err + } } var f32s []float32 diff --git a/convert/reader_torch.go b/convert/reader_torch.go index 1428706e2..531996bf2 100644 --- a/convert/reader_torch.go +++ b/convert/reader_torch.go @@ -2,12 +2,13 @@ package convert import ( "io" + "io/fs" "github.com/nlpodyssey/gopickle/pytorch" "github.com/nlpodyssey/gopickle/types" ) -func parseTorch(ps ...string) ([]Tensor, error) { +func parseTorch(fsys fs.FS, ps ...string) ([]Tensor, error) { var ts []Tensor for _, p := range ps { pt, err := pytorch.Load(p) diff --git a/convert/tokenizer.go b/convert/tokenizer.go index 43d8c14ef..cca40eb00 100644 --- a/convert/tokenizer.go +++ b/convert/tokenizer.go @@ -7,9 +7,9 @@ import ( "encoding/json" "errors" "fmt" + "io/fs" "log/slog" "os" - "path/filepath" "slices" ) @@ -32,8 +32,8 @@ type Tokenizer struct { Template string } -func parseTokenizer(d string, specialTokenTypes []string) (*Tokenizer, error) { - v, err := parseVocabulary(d) +func parseTokenizer(fsys fs.FS, specialTokenTypes []string) (*Tokenizer, error) { + v, err := parseVocabulary(fsys) if err != nil { return nil, err } @@ -44,7 +44,7 @@ func parseTokenizer(d string, specialTokenTypes []string) (*Tokenizer, error) { } addedTokens := make(map[string]token) - if f, err := os.Open(filepath.Join(d, "tokenizer.json")); errors.Is(err, os.ErrNotExist) { + if f, err := fsys.Open("tokenizer.json"); errors.Is(err, os.ErrNotExist) { } else if err != nil { return nil, err } else { @@ -87,7 +87,7 @@ func parseTokenizer(d string, specialTokenTypes []string) (*Tokenizer, error) { } } - if f, err := os.Open(filepath.Join(d, "tokenizer_config.json")); errors.Is(err, os.ErrNotExist) { + if f, err := fsys.Open("tokenizer_config.json"); errors.Is(err, os.ErrNotExist) { } else if err != nil { return nil, err } else { @@ -172,8 +172,8 @@ type Vocabulary struct { Types []int32 } -func parseVocabularyFromTokenizer(p string) (*Vocabulary, error) { - f, err := os.Open(filepath.Join(p, "tokenizer.json")) +func parseVocabularyFromTokenizer(fsys fs.FS) (*Vocabulary, error) { + f, err := fsys.Open("tokenizer.json") if err != nil { return nil, err } @@ -219,20 +219,20 @@ func parseVocabularyFromTokenizer(p string) (*Vocabulary, error) { return &v, nil } -func parseVocabulary(d string) (*Vocabulary, error) { - patterns := map[string]func(string) (*Vocabulary, error){ +func parseVocabulary(fsys fs.FS) (*Vocabulary, error) { + patterns := map[string]func(fs.FS) (*Vocabulary, error){ "tokenizer.model": parseSentencePiece, "tokenizer.json": parseVocabularyFromTokenizer, } for pattern, parseFn := range patterns { - if _, err := os.Stat(filepath.Join(d, pattern)); errors.Is(err, os.ErrNotExist) { + if _, err := fs.Stat(fsys, pattern); errors.Is(err, os.ErrNotExist) { continue } else if err != nil { return nil, err } - return parseFn(d) + return parseFn(fsys) } return nil, errors.New("unknown tensor format") diff --git a/convert/tokenizer_spm.go b/convert/tokenizer_spm.go index 75d9fe267..babf702c7 100644 --- a/convert/tokenizer_spm.go +++ b/convert/tokenizer_spm.go @@ -5,8 +5,8 @@ import ( "encoding/json" "errors" "fmt" + "io/fs" "os" - "path/filepath" "slices" "google.golang.org/protobuf/proto" @@ -14,8 +14,8 @@ import ( "github.com/ollama/ollama/convert/sentencepiece" ) -func parseSentencePiece(d string) (*Vocabulary, error) { - bts, err := os.ReadFile(filepath.Join(d, "tokenizer.model")) +func parseSentencePiece(fsys fs.FS) (*Vocabulary, error) { + bts, err := fs.ReadFile(fsys, "tokenizer.model") if err != nil { return nil, err } @@ -41,7 +41,7 @@ func parseSentencePiece(d string) (*Vocabulary, error) { } } - f, err := os.Open(filepath.Join(d, "added_tokens.json")) + f, err := fsys.Open("added_tokens.json") if errors.Is(err, os.ErrNotExist) { return &v, nil } else if err != nil { diff --git a/server/model.go b/server/model.go index 81272a34c..f2946a0be 100644 --- a/server/model.go +++ b/server/model.go @@ -81,88 +81,43 @@ func parseFromModel(ctx context.Context, name model.Name, fn func(api.ProgressRe return layers, nil } -func extractFromZipFile(p string, file *os.File, fn func(api.ProgressResponse)) error { - stat, err := file.Stat() - if err != nil { - return err - } - - r, err := zip.NewReader(file, stat.Size()) - if err != nil { - return err - } - - fn(api.ProgressResponse{Status: "unpacking model metadata"}) - for _, f := range r.File { - if !filepath.IsLocal(f.Name) { - return fmt.Errorf("%w: %s", zip.ErrInsecurePath, f.Name) - } - - n := filepath.Join(p, f.Name) - if err := os.MkdirAll(filepath.Dir(n), 0o750); err != nil { - return err - } - - // TODO(mxyng): this should not write out all files to disk - outfile, err := os.Create(n) - if err != nil { - return err - } - defer outfile.Close() - - infile, err := f.Open() - if err != nil { - return err - } - defer infile.Close() - - if _, err = io.Copy(outfile, infile); err != nil { - return err - } - - if err := outfile.Close(); err != nil { - return err - } - - if err := infile.Close(); err != nil { - return err - } - } - - return nil -} - -func parseFromZipFile(_ context.Context, file *os.File, digest string, fn func(api.ProgressResponse)) (layers []*layerGGML, err error) { - tempDir, err := os.MkdirTemp(filepath.Dir(file.Name()), "") +func parseFromZipFile(_ context.Context, f *os.File, digest string, fn func(api.ProgressResponse)) (layers []*layerGGML, err error) { + fi, err := f.Stat() if err != nil { return nil, err } - defer os.RemoveAll(tempDir) - if err := extractFromZipFile(tempDir, file, fn); err != nil { + r, err := zip.NewReader(f, fi.Size()) + if err != nil { return nil, err } + p, err := os.MkdirTemp(filepath.Dir(f.Name()), "") + if err != nil { + return nil, err + } + defer os.RemoveAll(p) + fn(api.ProgressResponse{Status: "converting model"}) - // TODO(mxyng): this should write directly into a layer // e.g. NewLayer(arch.Reader(), "application/vnd.ollama.image.model") - temp, err := os.CreateTemp(tempDir, "fp16") + t, err := os.CreateTemp(p, "fp16") if err != nil { return nil, err } - defer temp.Close() - defer os.Remove(temp.Name()) + defer t.Close() + defer os.Remove(t.Name()) - if err := convert.Convert(tempDir, temp); err != nil { + fn(api.ProgressResponse{Status: "converting model"}) + if err := convert.Convert(convert.NewZipReader(r, p, 32<<20), t); err != nil { return nil, err } - if _, err := temp.Seek(0, io.SeekStart); err != nil { + if _, err := t.Seek(0, io.SeekStart); err != nil { return nil, err } - layer, err := NewLayer(temp, "application/vnd.ollama.image.model") + layer, err := NewLayer(t, "application/vnd.ollama.image.model") if err != nil { return nil, err } diff --git a/server/model_test.go b/server/model_test.go index 5829adfce..0a2225d5b 100644 --- a/server/model_test.go +++ b/server/model_test.go @@ -1,16 +1,11 @@ package server import ( - "archive/zip" "bytes" "encoding/json" - "errors" "fmt" - "io" "os" "path/filepath" - "slices" - "strings" "testing" "github.com/google/go-cmp/cmp" @@ -18,103 +13,6 @@ import ( "github.com/ollama/ollama/template" ) -func createZipFile(t *testing.T, name string) *os.File { - t.Helper() - - f, err := os.CreateTemp(t.TempDir(), "") - if err != nil { - t.Fatal(err) - } - - zf := zip.NewWriter(f) - defer zf.Close() - - zh, err := zf.CreateHeader(&zip.FileHeader{Name: name}) - if err != nil { - t.Fatal(err) - } - - if _, err := io.Copy(zh, bytes.NewReader([]byte(""))); err != nil { - t.Fatal(err) - } - - return f -} - -func TestExtractFromZipFile(t *testing.T) { - cases := []struct { - name string - expect []string - err error - }{ - { - name: "good", - expect: []string{"good"}, - }, - { - name: strings.Join([]string{"path", "..", "to", "good"}, string(os.PathSeparator)), - expect: []string{filepath.Join("to", "good")}, - }, - { - name: strings.Join([]string{"path", "..", "to", "..", "good"}, string(os.PathSeparator)), - expect: []string{"good"}, - }, - { - name: strings.Join([]string{"path", "to", "..", "..", "good"}, string(os.PathSeparator)), - expect: []string{"good"}, - }, - { - name: strings.Join([]string{"..", "..", "..", "..", "..", "..", "..", "..", "..", "..", "..", "..", "..", "..", "..", "..", "bad"}, string(os.PathSeparator)), - err: zip.ErrInsecurePath, - }, - { - name: strings.Join([]string{"path", "..", "..", "to", "bad"}, string(os.PathSeparator)), - err: zip.ErrInsecurePath, - }, - } - - for _, tt := range cases { - t.Run(tt.name, func(t *testing.T) { - f := createZipFile(t, tt.name) - defer f.Close() - - tempDir := t.TempDir() - if err := extractFromZipFile(tempDir, f, func(api.ProgressResponse) {}); !errors.Is(err, tt.err) { - t.Fatal(err) - } - - var matches []string - if err := filepath.Walk(tempDir, func(p string, fi os.FileInfo, err error) error { - if err != nil { - return err - } - - if !fi.IsDir() { - matches = append(matches, p) - } - - return nil - }); err != nil { - t.Fatal(err) - } - - var actual []string - for _, match := range matches { - rel, err := filepath.Rel(tempDir, match) - if err != nil { - t.Error(err) - } - - actual = append(actual, rel) - } - - if !slices.Equal(actual, tt.expect) { - t.Fatalf("expected %d files, got %d", len(tt.expect), len(matches)) - } - }) - } -} - func readFile(t *testing.T, base, name string) *bytes.Buffer { t.Helper() From d8e2664c33e81af0549aa9e75c57e08317d0322d Mon Sep 17 00:00:00 2001 From: Michael Yang Date: Wed, 31 Jul 2024 15:39:11 -0700 Subject: [PATCH 184/384] convert: fix parse functions --- convert/reader.go | 21 ++++++++++++--------- convert/tokenizer.go | 15 +++++++++------ 2 files changed, 21 insertions(+), 15 deletions(-) diff --git a/convert/reader.go b/convert/reader.go index 56a8ae895..ce95208e1 100644 --- a/convert/reader.go +++ b/convert/reader.go @@ -56,22 +56,25 @@ func (t *tensorBase) SetRepacker(fn repacker) { type repacker func(string, []float32, []uint64) ([]float32, error) func parseTensors(fsys fs.FS) ([]Tensor, error) { - patterns := map[string]func(fs.FS, ...string) ([]Tensor, error){ - "model-*-of-*.safetensors": parseSafetensors, - "model.safetensors": parseSafetensors, - "pytorch_model-*-of-*.bin": parseTorch, - "pytorch_model.bin": parseTorch, - "consolidated.*.pth": parseTorch, + patterns := []struct { + Pattern string + Func func(fs.FS, ...string) ([]Tensor, error) + }{ + {"model-*-of-*.safetensors", parseSafetensors}, + {"model.safetensors", parseSafetensors}, + {"pytorch_model-*-of-*.bin", parseTorch}, + {"pytorch_model.bin", parseTorch}, + {"consolidated.*.pth", parseTorch}, } - for pattern, parseFn := range patterns { - matches, err := fs.Glob(fsys, pattern) + for _, pattern := range patterns { + matches, err := fs.Glob(fsys, pattern.Pattern) if err != nil { return nil, err } if len(matches) > 0 { - return parseFn(fsys, matches...) + return pattern.Func(fsys, matches...) } } diff --git a/convert/tokenizer.go b/convert/tokenizer.go index cca40eb00..0d42a6d8a 100644 --- a/convert/tokenizer.go +++ b/convert/tokenizer.go @@ -220,19 +220,22 @@ func parseVocabularyFromTokenizer(fsys fs.FS) (*Vocabulary, error) { } func parseVocabulary(fsys fs.FS) (*Vocabulary, error) { - patterns := map[string]func(fs.FS) (*Vocabulary, error){ - "tokenizer.model": parseSentencePiece, - "tokenizer.json": parseVocabularyFromTokenizer, + patterns := []struct { + Pattern string + Func func(fs.FS) (*Vocabulary, error) + }{ + {"tokenizer.model", parseSentencePiece}, + {"tokenizer.json", parseVocabularyFromTokenizer}, } - for pattern, parseFn := range patterns { - if _, err := fs.Stat(fsys, pattern); errors.Is(err, os.ErrNotExist) { + for _, pattern := range patterns { + if _, err := fs.Stat(fsys, pattern.Pattern); errors.Is(err, os.ErrNotExist) { continue } else if err != nil { return nil, err } - return parseFn(fsys) + return pattern.Func(fsys) } return nil, errors.New("unknown tensor format") From dc77bbcfa40dea8b8fc7713a2ecacbc6a9d08a25 Mon Sep 17 00:00:00 2001 From: Blake Mizerany Date: Wed, 31 Jul 2024 16:01:24 -0700 Subject: [PATCH 185/384] server: fix json marshalling of downloadBlobPart (#6108) --- server/download.go | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/server/download.go b/server/download.go index 45483ba68..10074554a 100644 --- a/server/download.go +++ b/server/download.go @@ -61,6 +61,36 @@ type blobDownloadPart struct { *blobDownload `json:"-"` } +type jsonBlobDownloadPart struct { + N int + Offset int64 + Size int64 + Completed int64 +} + +func (p *blobDownloadPart) MarshalJSON() ([]byte, error) { + return json.Marshal(jsonBlobDownloadPart{ + N: p.N, + Offset: p.Offset, + Size: p.Size, + Completed: p.Completed.Load(), + }) +} + +func (p *blobDownloadPart) UnmarshalJSON(b []byte) error { + var j jsonBlobDownloadPart + if err := json.Unmarshal(b, &j); err != nil { + return err + } + *p = blobDownloadPart{ + N: j.N, + Offset: j.Offset, + Size: j.Size, + } + p.Completed.Store(j.Completed) + return nil +} + const ( numDownloadParts = 64 minDownloadPartSize int64 = 100 * format.MegaByte From d87b4a488eaf6e52bc0ba170a803cbf4d63921cd Mon Sep 17 00:00:00 2001 From: Michael Yang Date: Wed, 31 Jul 2024 16:52:09 -0700 Subject: [PATCH 186/384] fix modelfile message quotes --- server/images.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/images.go b/server/images.go index 24675783c..5f3eee88b 100644 --- a/server/images.go +++ b/server/images.go @@ -184,7 +184,7 @@ func (m *Model) String() string { for _, msg := range m.Messages { modelfile.Commands = append(modelfile.Commands, parser.Command{ Name: "message", - Args: fmt.Sprintf("%s %s", msg.Role, msg.Content), + Args: fmt.Sprintf("%s: %s", msg.Role, msg.Content), }) } From 6bc5c137581cdd825627e7ec3da308843c94e162 Mon Sep 17 00:00:00 2001 From: Vyacheslav Moskalev Date: Thu, 1 Aug 2024 15:45:41 +0700 Subject: [PATCH 187/384] Fix extra context concatenation in generate handler (#5980). --- server/routes.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/routes.go b/server/routes.go index fb9b30911..b449136ea 100644 --- a/server/routes.go +++ b/server/routes.go @@ -247,7 +247,7 @@ func (s *Server) GenerateHandler(c *gin.Context) { ch <- gin.H{"error": err.Error()} return } - res.Context = append(req.Context, tokens...) + res.Context = tokens } } From 49a54831397d1723af4fbcd4f0c5c68dadbc54d5 Mon Sep 17 00:00:00 2001 From: Vyacheslav Moskalev Date: Thu, 1 Aug 2024 19:25:56 +0700 Subject: [PATCH 188/384] Change the order of context and prompt. --- server/routes.go | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/server/routes.go b/server/routes.go index b449136ea..65ba22eac 100644 --- a/server/routes.go +++ b/server/routes.go @@ -188,21 +188,22 @@ func (s *Server) GenerateHandler(c *gin.Context) { } var b bytes.Buffer - if err := tmpl.Execute(&b, values); err != nil { + var t bytes.Buffer + if err := tmpl.Execute(&t, values); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } if req.Context != nil { - s, err := r.Detokenize(c.Request.Context(), req.Context) + prev, err := r.Detokenize(c.Request.Context(), req.Context) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } - - b.WriteString(s) + b.WriteString(prev) } + b.WriteString(t.String()); prompt = b.String() } @@ -242,12 +243,12 @@ func (s *Server) GenerateHandler(c *gin.Context) { res.LoadDuration = checkpointLoaded.Sub(checkpointStart) if !req.Raw { - tokens, err := r.Tokenize(c.Request.Context(), prompt+sb.String()) + tokens, err := r.Tokenize(c.Request.Context(), prompt + sb.String()) if err != nil { ch <- gin.H{"error": err.Error()} return } - res.Context = tokens + res.Context = tokens[:] } } From b0c216584c82b47fa91323468a2c58e79f96f0bb Mon Sep 17 00:00:00 2001 From: Vyacheslav Moskalev Date: Thu, 1 Aug 2024 19:43:44 +0700 Subject: [PATCH 189/384] Better types and naming closer to style. --- server/routes.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/server/routes.go b/server/routes.go index 65ba22eac..0d397aaae 100644 --- a/server/routes.go +++ b/server/routes.go @@ -187,9 +187,9 @@ func (s *Server) GenerateHandler(c *gin.Context) { values.Messages = append(msgs, api.Message{Role: "user", Content: req.Prompt}) } + var s string var b bytes.Buffer - var t bytes.Buffer - if err := tmpl.Execute(&t, values); err != nil { + if err := tmpl.Execute(&b, values); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } @@ -200,11 +200,11 @@ func (s *Server) GenerateHandler(c *gin.Context) { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } - b.WriteString(prev) + s += prev } - b.WriteString(t.String()); - prompt = b.String() + s += b.String(); + prompt = s } slog.Debug("generate request", "prompt", prompt, "images", images) From 3b5210548e957c5011233ae0e114131413362188 Mon Sep 17 00:00:00 2001 From: Vyacheslav Moskalev Date: Thu, 1 Aug 2024 19:56:15 +0700 Subject: [PATCH 190/384] Refactor code. Remove extra variable. --- server/routes.go | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/server/routes.go b/server/routes.go index 0d397aaae..8184db75c 100644 --- a/server/routes.go +++ b/server/routes.go @@ -187,7 +187,6 @@ func (s *Server) GenerateHandler(c *gin.Context) { values.Messages = append(msgs, api.Message{Role: "user", Content: req.Prompt}) } - var s string var b bytes.Buffer if err := tmpl.Execute(&b, values); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) @@ -195,16 +194,15 @@ func (s *Server) GenerateHandler(c *gin.Context) { } if req.Context != nil { - prev, err := r.Detokenize(c.Request.Context(), req.Context) + s, err := r.Detokenize(c.Request.Context(), req.Context) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } - s += prev + prompt = s + b.String() + } else { + prompt = b.String(); } - - s += b.String(); - prompt = s } slog.Debug("generate request", "prompt", prompt, "images", images) From 8a9f946ca76d04cfc964a3ffe5b919c0fb51915b Mon Sep 17 00:00:00 2001 From: Vyacheslav Moskalev Date: Fri, 2 Aug 2024 03:50:05 +0700 Subject: [PATCH 191/384] Refactor and format code. --- server/routes.go | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/server/routes.go b/server/routes.go index 8184db75c..a745fb20f 100644 --- a/server/routes.go +++ b/server/routes.go @@ -188,21 +188,21 @@ func (s *Server) GenerateHandler(c *gin.Context) { } var b bytes.Buffer - if err := tmpl.Execute(&b, values); err != nil { - c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) - return - } - if req.Context != nil { s, err := r.Detokenize(c.Request.Context(), req.Context) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } - prompt = s + b.String() - } else { - prompt = b.String(); + b.WriteString(s) } + + if err := tmpl.Execute(&b, values); err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + + prompt = b.String() } slog.Debug("generate request", "prompt", prompt, "images", images) @@ -241,12 +241,12 @@ func (s *Server) GenerateHandler(c *gin.Context) { res.LoadDuration = checkpointLoaded.Sub(checkpointStart) if !req.Raw { - tokens, err := r.Tokenize(c.Request.Context(), prompt + sb.String()) + tokens, err := r.Tokenize(c.Request.Context(), prompt+sb.String()) if err != nil { ch <- gin.H{"error": err.Error()} return } - res.Context = tokens[:] + res.Context = tokens } } From f561eecfb864536058ee73cdaca93de2a5c8dc5d Mon Sep 17 00:00:00 2001 From: royjhan <65097070+royjhan@users.noreply.github.com> Date: Thu, 1 Aug 2024 18:48:44 -0400 Subject: [PATCH 192/384] Update OpenAI Compatibility Docs with /v1/models (#5151) * OpenAI Docs * Update docs/openai.md Co-authored-by: Jeffrey Morgan * Remove newline --------- Co-authored-by: Jeffrey Morgan --- docs/openai.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/docs/openai.md b/docs/openai.md index fee30f71d..701fbcdd6 100644 --- a/docs/openai.md +++ b/docs/openai.md @@ -27,6 +27,8 @@ chat_completion = client.chat.completions.create( ], model='llama3', ) + +list_completion = client.models.list() ``` ### OpenAI JavaScript library @@ -45,6 +47,8 @@ const chatCompletion = await openai.chat.completions.create({ messages: [{ role: 'user', content: 'Say this is a test' }], model: 'llama3', }) + +const listCompletion = await openai.models.list() ``` ### `curl` @@ -66,6 +70,7 @@ curl http://localhost:11434/v1/chat/completions \ ] }' +curl http://localhost:11434/v1/models ``` ## Endpoints @@ -103,6 +108,13 @@ curl http://localhost:11434/v1/chat/completions \ - [ ] `user` - [ ] `n` +### `/v1/models` + +#### Notes + +- `created` corresponds to when the model was last modified +- `owned_by` corresponds to the ollama username, defaulting to `"library"` + ## Models Before using a model, pull it locally `ollama pull`: From 6f133a0bdd1a768d7936f6bbc40d11af732eee6f Mon Sep 17 00:00:00 2001 From: royjhan <65097070+royjhan@users.noreply.github.com> Date: Thu, 1 Aug 2024 18:49:37 -0400 Subject: [PATCH 193/384] OpenAI: Add Usage to `v1/embeddings` (#5886) * add prompt tokens to embed response * rm slog * metrics * types * prompt n * clean up * reset submodule * add tokens to v1/embeddings * separate usage --- openai/openai.go | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/openai/openai.go b/openai/openai.go index 5bd806604..e66d94165 100644 --- a/openai/openai.go +++ b/openai/openai.go @@ -164,9 +164,15 @@ type ListCompletion struct { } type EmbeddingList struct { - Object string `json:"object"` - Data []Embedding `json:"data"` - Model string `json:"model"` + Object string `json:"object"` + Data []Embedding `json:"data"` + Model string `json:"model"` + Usage EmbeddingUsage `json:"usage,omitempty"` +} + +type EmbeddingUsage struct { + PromptTokens int `json:"prompt_tokens"` + TotalTokens int `json:"total_tokens"` } func NewError(code int, message string) ErrorResponse { @@ -332,6 +338,10 @@ func toEmbeddingList(model string, r api.EmbedResponse) EmbeddingList { Object: "list", Data: data, Model: model, + Usage: EmbeddingUsage{ + PromptTokens: r.PromptEvalCount, + TotalTokens: r.PromptEvalCount, + }, } } From ed52833bb129c15fb499ced542889a23a0c6d74e Mon Sep 17 00:00:00 2001 From: royjhan <65097070+royjhan@users.noreply.github.com> Date: Thu, 1 Aug 2024 18:58:13 -0400 Subject: [PATCH 194/384] Add to docs (#5309) --- docs/openai.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/docs/openai.md b/docs/openai.md index 701fbcdd6..e4a4af1e5 100644 --- a/docs/openai.md +++ b/docs/openai.md @@ -29,6 +29,8 @@ chat_completion = client.chat.completions.create( ) list_completion = client.models.list() + +model = client.models.retrieve("llama3") ``` ### OpenAI JavaScript library @@ -49,6 +51,8 @@ const chatCompletion = await openai.chat.completions.create({ }) const listCompletion = await openai.models.list() + +const model = await openai.models.retrieve("llama3"); ``` ### `curl` @@ -71,6 +75,8 @@ curl http://localhost:11434/v1/chat/completions \ }' curl http://localhost:11434/v1/models + +curl https://api.openai.com/v1/models/llama3 ``` ## Endpoints @@ -115,6 +121,13 @@ curl http://localhost:11434/v1/models - `created` corresponds to when the model was last modified - `owned_by` corresponds to the ollama username, defaulting to `"library"` +### `/v1/models/{model}` + +#### Notes + +- `created` corresponds to when the model was last modified +- `owned_by` corresponds to the ollama username, defaulting to `"library"` + ## Models Before using a model, pull it locally `ollama pull`: From 558a54b098dc5044f5c4167ede5c327c970185a2 Mon Sep 17 00:00:00 2001 From: royjhan <65097070+royjhan@users.noreply.github.com> Date: Thu, 1 Aug 2024 19:00:29 -0400 Subject: [PATCH 195/384] Update OpenAI Compatibility Docs with /v1/embeddings (#5470) * docs without usage * no usage * rm metric note --- docs/openai.md | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/docs/openai.md b/docs/openai.md index e4a4af1e5..291953291 100644 --- a/docs/openai.md +++ b/docs/openai.md @@ -31,6 +31,11 @@ chat_completion = client.chat.completions.create( list_completion = client.models.list() model = client.models.retrieve("llama3") + +embeddings = client.embeddings.create( + model="all-minilm", + input=["why is the sky blue?", "why is the grass green?"] +) ``` ### OpenAI JavaScript library @@ -53,6 +58,11 @@ const chatCompletion = await openai.chat.completions.create({ const listCompletion = await openai.models.list() const model = await openai.models.retrieve("llama3"); + +const embedding = await openai.embeddings.create({ + model: "all-minilm", + input: ["why is the sky blue?", "why is the grass green?"], +}); ``` ### `curl` @@ -77,6 +87,13 @@ curl http://localhost:11434/v1/chat/completions \ curl http://localhost:11434/v1/models curl https://api.openai.com/v1/models/llama3 + +curl http://localhost:11434/v1/embeddings \ + -H "Content-Type: application/json" \ + -d '{ + "model": "all-minilm", + "input": ["why is the sky blue?", "why is the grass green?"] + }' ``` ## Endpoints @@ -128,6 +145,20 @@ curl https://api.openai.com/v1/models/llama3 - `created` corresponds to when the model was last modified - `owned_by` corresponds to the ollama username, defaulting to `"library"` +### `/v1/embeddings` + +#### Supported request fields + +- [x] `model` +- [x] `input` + - [x] string + - [x] array of strings + - [ ] array of tokens + - [ ] array of token arrays +- [ ] `encoding format` +- [ ] `dimensions` +- [ ] `user` + ## Models Before using a model, pull it locally `ollama pull`: From ce1fb4447efc9958dcf279f7eb2ae6941bec1220 Mon Sep 17 00:00:00 2001 From: Kim Hallberg Date: Fri, 2 Aug 2024 01:31:47 +0200 Subject: [PATCH 196/384] Fix models/{model} URL (#6132) --- docs/openai.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/openai.md b/docs/openai.md index 291953291..b4443cb02 100644 --- a/docs/openai.md +++ b/docs/openai.md @@ -86,7 +86,7 @@ curl http://localhost:11434/v1/chat/completions \ curl http://localhost:11434/v1/models -curl https://api.openai.com/v1/models/llama3 +curl http://localhost:11434/v1/models/llama3 curl http://localhost:11434/v1/embeddings \ -H "Content-Type: application/json" \ From b732beba6a919b852539bb344b05e25c6a7c3c90 Mon Sep 17 00:00:00 2001 From: Michael Yang Date: Thu, 1 Aug 2024 14:52:15 -0700 Subject: [PATCH 197/384] lint --- .gitattributes | 1 + .github/workflows/test.yaml | 2 +- .golangci.yaml | 18 ++++++++++++++---- api/client.go | 3 ++- api/types_test.go | 4 ++-- app/lifecycle/getstarted_nonwindows.go | 4 ++-- app/lifecycle/getstarted_windows.go | 1 - app/lifecycle/logging.go | 2 +- app/lifecycle/logging_nonwindows.go | 2 +- app/lifecycle/logging_test.go | 4 ++-- app/lifecycle/server.go | 2 +- app/lifecycle/updater.go | 3 ++- app/lifecycle/updater_nonwindows.go | 4 ++-- app/lifecycle/updater_windows.go | 5 +++-- app/tray/tray_nonwindows.go | 4 ++-- app/tray/wintray/eventloop.go | 4 +--- app/tray/wintray/tray.go | 5 +++-- auth/auth.go | 3 ++- cmd/cmd.go | 2 +- cmd/interactive.go | 2 +- cmd/start_darwin.go | 4 ++-- cmd/start_default.go | 4 ++-- cmd/start_windows.go | 2 +- convert/convert_llama.go | 3 ++- convert/convert_test.go | 6 ++++-- convert/fs.go | 4 ++-- convert/reader_safetensors.go | 5 +++-- format/format.go | 3 ++- gpu/amd_common.go | 4 ++-- gpu/amd_hip_windows.go | 9 +++++---- gpu/amd_linux.go | 2 +- gpu/amd_windows.go | 6 +++--- gpu/assets.go | 4 ++-- gpu/gpu.go | 19 ++++++++++--------- gpu/gpu_darwin.go | 1 + gpu/gpu_info.h | 2 +- gpu/gpu_linux.go | 10 ++++++---- gpu/gpu_windows.go | 10 ++++++---- integration/utils_test.go | 6 +++--- llm/llm.go | 5 +++-- llm/memory_test.go | 5 +++-- llm/server.go | 14 +++++++------- main.go | 3 ++- openai/openai.go | 20 ++++++++++---------- openai/openai_test.go | 11 +++++++---- parser/parser_test.go | 12 ++++++------ progress/bar.go | 3 ++- readline/buffer.go | 4 ++-- readline/errors.go | 4 +--- readline/term_linux.go | 6 ++++-- server/download.go | 6 ++++-- server/images.go | 6 +++--- server/manifest.go | 4 ++-- server/manifest_test.go | 2 +- server/model_test.go | 1 + server/prompt_test.go | 1 + server/routes.go | 14 +++++++------- server/routes_create_test.go | 7 ++++--- server/routes_delete_test.go | 1 + server/routes_list_test.go | 1 + server/routes_test.go | 1 - server/sched.go | 2 +- server/sched_test.go | 18 ++++++++++++------ server/upload.go | 6 ++++-- template/template.go | 3 ++- template/template_test.go | 1 + types/errtypes/errtypes.go | 6 ++++-- types/model/name.go | 2 +- 68 files changed, 199 insertions(+), 149 deletions(-) diff --git a/.gitattributes b/.gitattributes index a8436e9cf..f7192096c 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1 +1,2 @@ llm/ext_server/* linguist-vendored +* text eol=lf diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 5e002a225..a57d45fd7 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -273,7 +273,7 @@ jobs: if: ${{ startsWith(matrix.os, 'macos-') }} - uses: golangci/golangci-lint-action@v6 with: - args: --timeout 8m0s -v ${{ startsWith(matrix.os, 'windows-') && '' || '--disable gofmt --disable goimports' }} + args: --timeout 8m0s -v test: strategy: matrix: diff --git a/.golangci.yaml b/.golangci.yaml index cfe06e07a..c2c8b52b2 100644 --- a/.golangci.yaml +++ b/.golangci.yaml @@ -7,22 +7,32 @@ linters: - bodyclose - containedctx - contextcheck + - errcheck - exportloopref + - gci - gocheckcompilerdirectives - # conditionally enable this on linux/macos - # - gofmt - # - goimports + - gofmt + - gofumpt + - gosimple + - govet + - ineffassign - intrange + - makezero - misspell - nilerr - nolintlint - nosprintfhostport + - staticcheck + - tenv - testifylint - unconvert - unused + - usestdlibvars - wastedassign - whitespace - - usestdlibvars +linters-settings: + gci: + sections: [standard, default, localmodule] severity: default-severity: error rules: diff --git a/api/client.go b/api/client.go index e02b21bfa..bbdf8202f 100644 --- a/api/client.go +++ b/api/client.go @@ -18,6 +18,7 @@ import ( "bytes" "context" "encoding/json" + "errors" "fmt" "io" "net/http" @@ -172,7 +173,7 @@ func (c *Client) stream(ctx context.Context, method, path string, data any, fn f } if errorResponse.Error != "" { - return fmt.Errorf(errorResponse.Error) + return errors.New(errorResponse.Error) } if response.StatusCode >= http.StatusBadRequest { diff --git a/api/types_test.go b/api/types_test.go index 4699c1503..a9de5a9a9 100644 --- a/api/types_test.go +++ b/api/types_test.go @@ -2,7 +2,7 @@ package api import ( "encoding/json" - "fmt" + "errors" "math" "testing" "time" @@ -192,7 +192,7 @@ func TestUseMmapFormatParams(t *testing.T) { "use_mmap": {"foo"}, }, exp: nil, - err: fmt.Errorf("invalid bool value [foo]"), + err: errors.New("invalid bool value [foo]"), }, } diff --git a/app/lifecycle/getstarted_nonwindows.go b/app/lifecycle/getstarted_nonwindows.go index c36d14c09..2af87ab92 100644 --- a/app/lifecycle/getstarted_nonwindows.go +++ b/app/lifecycle/getstarted_nonwindows.go @@ -2,8 +2,8 @@ package lifecycle -import "fmt" +import "errors" func GetStarted() error { - return fmt.Errorf("GetStarted not implemented") + return errors.New("not implemented") } diff --git a/app/lifecycle/getstarted_windows.go b/app/lifecycle/getstarted_windows.go index 092c3c17f..f39dc31c0 100644 --- a/app/lifecycle/getstarted_windows.go +++ b/app/lifecycle/getstarted_windows.go @@ -34,7 +34,6 @@ func GetStarted() error { Sys: &syscall.SysProcAttr{CreationFlags: CREATE_NEW_CONSOLE, HideWindow: false}, } proc, err := os.StartProcess(args[0], args, attrs) - if err != nil { return fmt.Errorf("unable to start getting started shell %w", err) } diff --git a/app/lifecycle/logging.go b/app/lifecycle/logging.go index 3672aad59..9985fc3f8 100644 --- a/app/lifecycle/logging.go +++ b/app/lifecycle/logging.go @@ -27,7 +27,7 @@ func InitLogging() { // TODO - write one-line to the app.log file saying we're running in console mode to help avoid confusion } else { rotateLogs(AppLogFile) - logFile, err = os.OpenFile(AppLogFile, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0755) + logFile, err = os.OpenFile(AppLogFile, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0o755) if err != nil { slog.Error(fmt.Sprintf("failed to create server log %v", err)) return diff --git a/app/lifecycle/logging_nonwindows.go b/app/lifecycle/logging_nonwindows.go index 50b3a638c..205e47d77 100644 --- a/app/lifecycle/logging_nonwindows.go +++ b/app/lifecycle/logging_nonwindows.go @@ -5,5 +5,5 @@ package lifecycle import "log/slog" func ShowLogs() { - slog.Warn("ShowLogs not yet implemented") + slog.Warn("not implemented") } diff --git a/app/lifecycle/logging_test.go b/app/lifecycle/logging_test.go index a2157ca2c..8d5cdf6e7 100644 --- a/app/lifecycle/logging_test.go +++ b/app/lifecycle/logging_test.go @@ -17,7 +17,7 @@ func TestRotateLogs(t *testing.T) { // No log exists rotateLogs(logFile) - require.NoError(t, os.WriteFile(logFile, []byte("1"), 0644)) + require.NoError(t, os.WriteFile(logFile, []byte("1"), 0o644)) assert.FileExists(t, logFile) // First rotation rotateLogs(logFile) @@ -32,7 +32,7 @@ func TestRotateLogs(t *testing.T) { assert.NoFileExists(t, logFile) for i := 2; i <= LogRotationCount+1; i++ { - require.NoError(t, os.WriteFile(logFile, []byte(strconv.Itoa(i)), 0644)) + require.NoError(t, os.WriteFile(logFile, []byte(strconv.Itoa(i)), 0o644)) assert.FileExists(t, logFile) rotateLogs(logFile) assert.NoFileExists(t, logFile) diff --git a/app/lifecycle/server.go b/app/lifecycle/server.go index c178a1abf..37957399c 100644 --- a/app/lifecycle/server.go +++ b/app/lifecycle/server.go @@ -55,7 +55,7 @@ func start(ctx context.Context, command string) (*exec.Cmd, error) { } rotateLogs(ServerLogFile) - logFile, err := os.OpenFile(ServerLogFile, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0755) + logFile, err := os.OpenFile(ServerLogFile, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0o755) if err != nil { return nil, fmt.Errorf("failed to create server log: %w", err) } diff --git a/app/lifecycle/updater.go b/app/lifecycle/updater.go index b6d953309..4d3c7d8dc 100644 --- a/app/lifecycle/updater.go +++ b/app/lifecycle/updater.go @@ -15,6 +15,7 @@ import ( "path" "path/filepath" "runtime" + "strconv" "strings" "time" @@ -46,7 +47,7 @@ func IsNewReleaseAvailable(ctx context.Context) (bool, UpdateResponse) { query.Add("os", runtime.GOOS) query.Add("arch", runtime.GOARCH) query.Add("version", version.Version) - query.Add("ts", fmt.Sprintf("%d", time.Now().Unix())) + query.Add("ts", strconv.FormatInt(time.Now().Unix(), 10)) nonce, err := auth.NewNonce(rand.Reader, 16) if err != nil { diff --git a/app/lifecycle/updater_nonwindows.go b/app/lifecycle/updater_nonwindows.go index 0f213b34f..1d2dda801 100644 --- a/app/lifecycle/updater_nonwindows.go +++ b/app/lifecycle/updater_nonwindows.go @@ -4,9 +4,9 @@ package lifecycle import ( "context" - "fmt" + "errors" ) func DoUpgrade(cancel context.CancelFunc, done chan int) error { - return fmt.Errorf("DoUpgrade not yet implemented") + return errors.New("not implemented") } diff --git a/app/lifecycle/updater_windows.go b/app/lifecycle/updater_windows.go index 4053671a5..1d3830d4e 100644 --- a/app/lifecycle/updater_windows.go +++ b/app/lifecycle/updater_windows.go @@ -2,6 +2,7 @@ package lifecycle import ( "context" + "errors" "fmt" "log/slog" "os" @@ -15,7 +16,7 @@ func DoUpgrade(cancel context.CancelFunc, done chan int) error { return fmt.Errorf("failed to lookup downloads: %s", err) } if len(files) == 0 { - return fmt.Errorf("no update downloads found") + return errors.New("no update downloads found") } else if len(files) > 1 { // Shouldn't happen slog.Warn(fmt.Sprintf("multiple downloads found, using first one %v", files)) @@ -64,7 +65,7 @@ func DoUpgrade(cancel context.CancelFunc, done chan int) error { } } else { // TODO - some details about why it didn't start, or is this a pedantic error case? - return fmt.Errorf("installer process did not start") + return errors.New("installer process did not start") } // TODO should we linger for a moment and check to make sure it's actually running by checking the pid? diff --git a/app/tray/tray_nonwindows.go b/app/tray/tray_nonwindows.go index ae5572b23..a03d233ea 100644 --- a/app/tray/tray_nonwindows.go +++ b/app/tray/tray_nonwindows.go @@ -3,11 +3,11 @@ package tray import ( - "fmt" + "errors" "github.com/ollama/ollama/app/tray/commontray" ) func InitPlatformTray(icon, updateIcon []byte) (commontray.OllamaTray, error) { - return nil, fmt.Errorf("NOT IMPLEMENTED YET") + return nil, errors.New("not implemented") } diff --git a/app/tray/wintray/eventloop.go b/app/tray/wintray/eventloop.go index 0f9448947..157828a36 100644 --- a/app/tray/wintray/eventloop.go +++ b/app/tray/wintray/eventloop.go @@ -11,9 +11,7 @@ import ( "golang.org/x/sys/windows" ) -var ( - quitOnce sync.Once -) +var quitOnce sync.Once func (t *winTray) Run() { nativeLoop() diff --git a/app/tray/wintray/tray.go b/app/tray/wintray/tray.go index 027ec5a50..ccd087a17 100644 --- a/app/tray/wintray/tray.go +++ b/app/tray/wintray/tray.go @@ -13,8 +13,9 @@ import ( "sync" "unsafe" - "github.com/ollama/ollama/app/tray/commontray" "golang.org/x/sys/windows" + + "github.com/ollama/ollama/app/tray/commontray" ) // Helpful sources: https://github.com/golang/exp/blob/master/shiny/driver/internal/win32 @@ -414,7 +415,7 @@ func iconBytesToFilePath(iconBytes []byte) (string, error) { iconFilePath := filepath.Join(os.TempDir(), "ollama_temp_icon_"+dataHash) if _, err := os.Stat(iconFilePath); os.IsNotExist(err) { - if err := os.WriteFile(iconFilePath, iconBytes, 0644); err != nil { + if err := os.WriteFile(iconFilePath, iconBytes, 0o644); err != nil { return "", err } } diff --git a/auth/auth.go b/auth/auth.go index 026b2a2c7..e1d854124 100644 --- a/auth/auth.go +++ b/auth/auth.go @@ -5,6 +5,7 @@ import ( "context" "crypto/rand" "encoding/base64" + "errors" "fmt" "io" "log/slog" @@ -78,7 +79,7 @@ func Sign(ctx context.Context, bts []byte) (string, error) { publicKey := ssh.MarshalAuthorizedKey(privateKey.PublicKey()) parts := bytes.Split(publicKey, []byte(" ")) if len(parts) < 2 { - return "", fmt.Errorf("malformed public key") + return "", errors.New("malformed public key") } signedData, err := privateKey.Sign(rand.Reader, bts) diff --git a/cmd/cmd.go b/cmd/cmd.go index c1a3c3f6c..d47db65b3 100644 --- a/cmd/cmd.go +++ b/cmd/cmd.go @@ -1160,7 +1160,7 @@ func checkServerHeartbeat(cmd *cobra.Command, _ []string) error { return err } if err := startApp(cmd.Context(), client); err != nil { - return fmt.Errorf("could not connect to ollama app, is it running?") + return errors.New("could not connect to ollama app, is it running?") } } return nil diff --git a/cmd/interactive.go b/cmd/interactive.go index b566eb2f2..4462cf29d 100644 --- a/cmd/interactive.go +++ b/cmd/interactive.go @@ -604,7 +604,7 @@ func getImageData(filePath string) ([]byte, error) { // Check if the file size exceeds 100MB var maxSize int64 = 100 * 1024 * 1024 // 100MB in bytes if info.Size() > maxSize { - return nil, fmt.Errorf("file size exceeds maximum limit (100MB)") + return nil, errors.New("file size exceeds maximum limit (100MB)") } buf = make([]byte, info.Size()) diff --git a/cmd/start_darwin.go b/cmd/start_darwin.go index 82b09ad62..1a9a1ae87 100644 --- a/cmd/start_darwin.go +++ b/cmd/start_darwin.go @@ -2,7 +2,7 @@ package cmd import ( "context" - "fmt" + "errors" "os" "os/exec" "strings" @@ -20,7 +20,7 @@ func startApp(ctx context.Context, client *api.Client) error { return err } if !strings.Contains(link, "Ollama.app") { - return fmt.Errorf("could not find ollama app") + return errors.New("could not find ollama app") } path := strings.Split(link, "Ollama.app") if err := exec.Command("/usr/bin/open", "-a", path[0]+"Ollama.app").Run(); err != nil { diff --git a/cmd/start_default.go b/cmd/start_default.go index c9d6137b9..5eabb2862 100644 --- a/cmd/start_default.go +++ b/cmd/start_default.go @@ -4,11 +4,11 @@ package cmd import ( "context" - "fmt" + "errors" "github.com/ollama/ollama/api" ) func startApp(ctx context.Context, client *api.Client) error { - return fmt.Errorf("could not connect to ollama server, run 'ollama serve' to start it") + return errors.New("could not connect to ollama server, run 'ollama serve' to start it") } diff --git a/cmd/start_windows.go b/cmd/start_windows.go index 6024a2352..5bca24331 100644 --- a/cmd/start_windows.go +++ b/cmd/start_windows.go @@ -31,7 +31,7 @@ func startApp(ctx context.Context, client *api.Client) error { // Finally look in the path appExe, err = exec.LookPath(AppName) if err != nil { - return fmt.Errorf("could not locate ollama app") + return errors.New("could not locate ollama app") } } } diff --git a/convert/convert_llama.go b/convert/convert_llama.go index ed6469c5e..0383a85eb 100644 --- a/convert/convert_llama.go +++ b/convert/convert_llama.go @@ -5,9 +5,10 @@ import ( "fmt" "strings" - "github.com/ollama/ollama/llm" "github.com/pdevine/tensor" "github.com/pdevine/tensor/native" + + "github.com/ollama/ollama/llm" ) type llama struct { diff --git a/convert/convert_test.go b/convert/convert_test.go index 67a2fcfe3..88f38494e 100644 --- a/convert/convert_test.go +++ b/convert/convert_test.go @@ -2,6 +2,7 @@ package convert import ( "crypto/sha256" + "encoding/hex" "encoding/json" "flag" "fmt" @@ -14,8 +15,9 @@ import ( "slices" "testing" - "github.com/ollama/ollama/llm" "golang.org/x/exp/maps" + + "github.com/ollama/ollama/llm" ) func convertFull(t *testing.T, fsys fs.FS) (*os.File, llm.KV, llm.Tensors) { @@ -99,7 +101,7 @@ func TestConvertFull(t *testing.T) { t.Fatal(err) } - actual[tensor.Name] = fmt.Sprintf("%x", sha256sum.Sum(nil)) + actual[tensor.Name] = hex.EncodeToString(sha256sum.Sum(nil)) } expectFile, err := os.Open(filepath.Join("testdata", fmt.Sprintf("%s.json", tt))) diff --git a/convert/fs.go b/convert/fs.go index bf6da6c25..31132dbe7 100644 --- a/convert/fs.go +++ b/convert/fs.go @@ -10,8 +10,8 @@ import ( ) type ZipReader struct { - r *zip.Reader - p string + r *zip.Reader + p string // limit is the maximum size of a file that can be read directly // from the zip archive. Files larger than this size will be extracted diff --git a/convert/reader_safetensors.go b/convert/reader_safetensors.go index 1c1695044..42f902a5a 100644 --- a/convert/reader_safetensors.go +++ b/convert/reader_safetensors.go @@ -111,8 +111,9 @@ func (st safetensor) WriteTo(w io.Writer) (int64, error) { return 0, err } - for _, b := range u16s { - f32s = append(f32s, float16.Frombits(b).Float32()) + f32s = make([]float32, len(u16s)) + for i := range u16s { + f32s[i] = float16.Frombits(u16s[i]).Float32() } case "BF16": diff --git a/format/format.go b/format/format.go index 31059578f..ac50570df 100644 --- a/format/format.go +++ b/format/format.go @@ -3,6 +3,7 @@ package format import ( "fmt" "math" + "strconv" ) const ( @@ -28,6 +29,6 @@ func HumanNumber(b uint64) string { case b >= Thousand: return fmt.Sprintf("%.0fK", float64(b)/Thousand) default: - return fmt.Sprintf("%d", b) + return strconv.FormatUint(b, 10) } } diff --git a/gpu/amd_common.go b/gpu/amd_common.go index 7d1cab7c1..2839cb7c3 100644 --- a/gpu/amd_common.go +++ b/gpu/amd_common.go @@ -3,7 +3,7 @@ package gpu import ( - "fmt" + "errors" "log/slog" "os" "path/filepath" @@ -95,5 +95,5 @@ func commonAMDValidateLibDir() (string, error) { } } - return "", fmt.Errorf("no suitable rocm found, falling back to CPU") + return "", errors.New("no suitable rocm found, falling back to CPU") } diff --git a/gpu/amd_hip_windows.go b/gpu/amd_hip_windows.go index 98806234c..2cea28242 100644 --- a/gpu/amd_hip_windows.go +++ b/gpu/amd_hip_windows.go @@ -1,6 +1,7 @@ package gpu import ( + "errors" "fmt" "log/slog" "syscall" @@ -76,7 +77,7 @@ func (hl *HipLib) Release() { func (hl *HipLib) AMDDriverVersion() (driverMajor, driverMinor int, err error) { if hl.dll == 0 { - return 0, 0, fmt.Errorf("dll has been unloaded") + return 0, 0, errors.New("dll has been unloaded") } var version int status, _, err := syscall.SyscallN(hl.hipDriverGetVersion, uintptr(unsafe.Pointer(&version))) @@ -110,7 +111,7 @@ func (hl *HipLib) HipGetDeviceCount() int { func (hl *HipLib) HipSetDevice(device int) error { if hl.dll == 0 { - return fmt.Errorf("dll has been unloaded") + return errors.New("dll has been unloaded") } status, _, err := syscall.SyscallN(hl.hipSetDevice, uintptr(device)) if status != hipSuccess { @@ -121,7 +122,7 @@ func (hl *HipLib) HipSetDevice(device int) error { func (hl *HipLib) HipGetDeviceProperties(device int) (*hipDevicePropMinimal, error) { if hl.dll == 0 { - return nil, fmt.Errorf("dll has been unloaded") + return nil, errors.New("dll has been unloaded") } var props hipDevicePropMinimal status, _, err := syscall.SyscallN(hl.hipGetDeviceProperties, uintptr(unsafe.Pointer(&props)), uintptr(device)) @@ -134,7 +135,7 @@ func (hl *HipLib) HipGetDeviceProperties(device int) (*hipDevicePropMinimal, err // free, total, err func (hl *HipLib) HipMemGetInfo() (uint64, uint64, error) { if hl.dll == 0 { - return 0, 0, fmt.Errorf("dll has been unloaded") + return 0, 0, errors.New("dll has been unloaded") } var totalMemory uint64 var freeMemory uint64 diff --git a/gpu/amd_linux.go b/gpu/amd_linux.go index 1ad4b906e..aab67efe3 100644 --- a/gpu/amd_linux.go +++ b/gpu/amd_linux.go @@ -393,7 +393,7 @@ func AMDValidateLibDir() (string, error) { // If we still haven't found a usable rocm, the user will have to install it on their own slog.Warn("amdgpu detected, but no compatible rocm library found. Either install rocm v6, or follow manual install instructions at https://github.com/ollama/ollama/blob/main/docs/linux.md#manual-install") - return "", fmt.Errorf("no suitable rocm found, falling back to CPU") + return "", errors.New("no suitable rocm found, falling back to CPU") } func AMDDriverVersion() (driverMajor, driverMinor int, err error) { diff --git a/gpu/amd_windows.go b/gpu/amd_windows.go index a170dfdcc..edabeb43a 100644 --- a/gpu/amd_windows.go +++ b/gpu/amd_windows.go @@ -2,7 +2,7 @@ package gpu import ( "bytes" - "fmt" + "errors" "log/slog" "os" "path/filepath" @@ -85,7 +85,7 @@ func AMDGetGPUInfo() []RocmGPUInfo { n = bytes.IndexByte(props.GcnArchName[:], 0) gfx := string(props.GcnArchName[:n]) slog.Debug("hip device", "id", i, "name", name, "gfx", gfx) - //slog.Info(fmt.Sprintf("[%d] Integrated: %d", i, props.iGPU)) // DOESN'T REPORT CORRECTLY! Always 0 + // slog.Info(fmt.Sprintf("[%d] Integrated: %d", i, props.iGPU)) // DOESN'T REPORT CORRECTLY! Always 0 // TODO Why isn't props.iGPU accurate!? if strings.EqualFold(name, iGPUName) { slog.Info("unsupported Radeon iGPU detected skipping", "id", i, "name", name, "gfx", gfx) @@ -161,7 +161,7 @@ func AMDValidateLibDir() (string, error) { // Should not happen on windows since we include it in the installer, but stand-alone binary might hit this slog.Warn("amdgpu detected, but no compatible rocm library found. Please install ROCm") - return "", fmt.Errorf("no suitable rocm found, falling back to CPU") + return "", errors.New("no suitable rocm found, falling back to CPU") } func (gpus RocmGPUInfoList) RefreshFreeMemory() error { diff --git a/gpu/assets.go b/gpu/assets.go index 39ff7c21a..a35b6630b 100644 --- a/gpu/assets.go +++ b/gpu/assets.go @@ -42,7 +42,7 @@ func PayloadsDir() (string, error) { return "", fmt.Errorf("failed to generate tmp dir: %w", err) } } else { - err = os.MkdirAll(tmpDir, 0755) + err = os.MkdirAll(tmpDir, 0o755) if err != nil { return "", fmt.Errorf("failed to generate tmp dir %s: %w", tmpDir, err) } @@ -54,7 +54,7 @@ func PayloadsDir() (string, error) { if err != nil { return "", err } - if _, err := pidFile.Write([]byte(fmt.Sprint(os.Getpid()))); err != nil { + if _, err := pidFile.Write([]byte(strconv.Itoa(os.Getpid()))); err != nil { return "", err } diff --git a/gpu/gpu.go b/gpu/gpu.go index acab1c8dd..7ae8fbec1 100644 --- a/gpu/gpu.go +++ b/gpu/gpu.go @@ -7,9 +7,9 @@ package gpu #cgo windows LDFLAGS: -lpthread #include "gpu_info.h" - */ import "C" + import ( "fmt" "log/slog" @@ -70,7 +70,6 @@ var CudaTegra string = os.Getenv("JETSON_JETPACK") // Note: gpuMutex must already be held func initCudaHandles() *cudaHandles { - // TODO - if the ollama build is CPU only, don't do these checks as they're irrelevant and confusing cHandles := &cudaHandles{} @@ -211,14 +210,16 @@ func GetGPUInfo() GpuInfoList { if err != nil { slog.Warn("error looking up system memory", "error", err) } - cpus = []CPUInfo{CPUInfo{ - GpuInfo: GpuInfo{ - memInfo: mem, - Library: "cpu", - Variant: cpuCapability, - ID: "0", + cpus = []CPUInfo{ + { + GpuInfo: GpuInfo{ + memInfo: mem, + Library: "cpu", + Variant: cpuCapability, + ID: "0", + }, }, - }} + } // Fallback to CPU mode if we're lacking required vector extensions on x86 if cpuCapability < GPURunnerCPUCapability && runtime.GOARCH == "amd64" { diff --git a/gpu/gpu_darwin.go b/gpu/gpu_darwin.go index cb066e581..9d9fd84eb 100644 --- a/gpu/gpu_darwin.go +++ b/gpu/gpu_darwin.go @@ -8,6 +8,7 @@ package gpu #include "gpu_info_darwin.h" */ import "C" + import ( "runtime" diff --git a/gpu/gpu_info.h b/gpu/gpu_info.h index ab0952d9a..094b791a8 100644 --- a/gpu/gpu_info.h +++ b/gpu/gpu_info.h @@ -67,4 +67,4 @@ void cpu_check_ram(mem_info_t *resp); #include "gpu_info_oneapi.h" #endif // __GPU_INFO_H__ -#endif // __APPLE__ \ No newline at end of file +#endif // __APPLE__ diff --git a/gpu/gpu_linux.go b/gpu/gpu_linux.go index 0d08ce8da..d6d2675c8 100644 --- a/gpu/gpu_linux.go +++ b/gpu/gpu_linux.go @@ -43,10 +43,12 @@ var OneapiGlobs = []string{ "/usr/lib*/libze_intel_gpu.so*", } -var CudartMgmtName = "libcudart.so*" -var NvcudaMgmtName = "libcuda.so*" -var NvmlMgmtName = "" // not currently wired on linux -var OneapiMgmtName = "libze_intel_gpu.so" +var ( + CudartMgmtName = "libcudart.so*" + NvcudaMgmtName = "libcuda.so*" + NvmlMgmtName = "" // not currently wired on linux + OneapiMgmtName = "libze_intel_gpu.so" +) func GetCPUMem() (memInfo, error) { var mem memInfo diff --git a/gpu/gpu_windows.go b/gpu/gpu_windows.go index cd0629da4..2ec72ba7b 100644 --- a/gpu/gpu_windows.go +++ b/gpu/gpu_windows.go @@ -40,10 +40,12 @@ var OneapiGlobs = []string{ "c:\\Windows\\System32\\DriverStore\\FileRepository\\*\\ze_intel_gpu64.dll", } -var CudartMgmtName = "cudart64_*.dll" -var NvcudaMgmtName = "nvcuda.dll" -var NvmlMgmtName = "nvml.dll" -var OneapiMgmtName = "ze_intel_gpu64.dll" +var ( + CudartMgmtName = "cudart64_*.dll" + NvcudaMgmtName = "nvcuda.dll" + NvmlMgmtName = "nvml.dll" + OneapiMgmtName = "ze_intel_gpu64.dll" +) func GetCPUMem() (memInfo, error) { memStatus := MEMORYSTATUSEX{length: sizeofMemoryStatusEx} diff --git a/integration/utils_test.go b/integration/utils_test.go index 7e1fcc10e..c2b27ee93 100644 --- a/integration/utils_test.go +++ b/integration/utils_test.go @@ -162,7 +162,7 @@ func PullIfMissing(ctx context.Context, client *api.Client, modelName string) er fn := func(resp api.ProgressResponse) error { // fmt.Print(".") if !stallTimer.Reset(stallDuration) { - return fmt.Errorf("stall was detected, aborting status reporting") + return errors.New("stall was detected, aborting status reporting") } return nil } @@ -180,7 +180,7 @@ func PullIfMissing(ctx context.Context, client *api.Client, modelName string) er select { case <-stallTimer.C: - return fmt.Errorf("download stalled") + return errors.New("download stalled") case <-done: return pullError } @@ -243,7 +243,7 @@ func DoGenerate(ctx context.Context, t *testing.T, client *api.Client, genReq ap // fmt.Print(".") buf.Write([]byte(response.Response)) if !stallTimer.Reset(streamTimeout) { - return fmt.Errorf("stall was detected while streaming response, aborting") + return errors.New("stall was detected while streaming response, aborting") } return nil } diff --git a/llm/llm.go b/llm/llm.go index d24507cce..6bb6591d8 100644 --- a/llm/llm.go +++ b/llm/llm.go @@ -11,8 +11,9 @@ package llm // #include // #include "llama.h" import "C" + import ( - "fmt" + "errors" "unsafe" ) @@ -33,7 +34,7 @@ func Quantize(infile, outfile string, ftype fileType) error { params.ftype = ftype.Value() if rc := C.llama_model_quantize(cinfile, coutfile, ¶ms); rc != 0 { - return fmt.Errorf("failed to quantize model. This model architecture may not be supported, or you may need to upgrade Ollama to the latest version") + return errors.New("failed to quantize model. This model architecture may not be supported, or you may need to upgrade Ollama to the latest version") } return nil diff --git a/llm/memory_test.go b/llm/memory_test.go index 3220c8df6..6cf0119f9 100644 --- a/llm/memory_test.go +++ b/llm/memory_test.go @@ -6,10 +6,11 @@ import ( "os" "testing" - "github.com/ollama/ollama/api" - "github.com/ollama/ollama/gpu" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/ollama/ollama/api" + "github.com/ollama/ollama/gpu" ) func TestEstimateGPULayers(t *testing.T) { diff --git a/llm/server.go b/llm/server.go index 7fadb0c95..7abc3bd72 100644 --- a/llm/server.go +++ b/llm/server.go @@ -184,15 +184,15 @@ func NewLlamaServer(gpus gpu.GpuInfoList, model string, ggml *GGML, adapters, pr params := []string{ "--model", model, - "--ctx-size", fmt.Sprintf("%d", opts.NumCtx), - "--batch-size", fmt.Sprintf("%d", opts.NumBatch), + "--ctx-size", strconv.Itoa(opts.NumCtx), + "--batch-size", strconv.Itoa(opts.NumBatch), "--embedding", } params = append(params, "--log-disable") if opts.NumGPU >= 0 { - params = append(params, "--n-gpu-layers", fmt.Sprintf("%d", opts.NumGPU)) + params = append(params, "--n-gpu-layers", strconv.Itoa(opts.NumGPU)) } if envconfig.Debug() { @@ -200,7 +200,7 @@ func NewLlamaServer(gpus gpu.GpuInfoList, model string, ggml *GGML, adapters, pr } if opts.MainGPU > 0 { - params = append(params, "--main-gpu", fmt.Sprintf("%d", opts.MainGPU)) + params = append(params, "--main-gpu", strconv.Itoa(opts.MainGPU)) } if len(adapters) > 0 { @@ -214,7 +214,7 @@ func NewLlamaServer(gpus gpu.GpuInfoList, model string, ggml *GGML, adapters, pr } if opts.NumThread > 0 { - params = append(params, "--threads", fmt.Sprintf("%d", opts.NumThread)) + params = append(params, "--threads", strconv.Itoa(opts.NumThread)) } if !opts.F16KV { @@ -260,7 +260,7 @@ func NewLlamaServer(gpus gpu.GpuInfoList, model string, ggml *GGML, adapters, pr params = append(params, "--numa") } - params = append(params, "--parallel", fmt.Sprintf("%d", numParallel)) + params = append(params, "--parallel", strconv.Itoa(numParallel)) if estimate.TensorSplit != "" { params = append(params, "--tensor-split", estimate.TensorSplit) @@ -425,7 +425,7 @@ func NewLlamaServer(gpus gpu.GpuInfoList, model string, ggml *GGML, adapters, pr if strings.Contains(s.status.LastErrMsg, "unknown model") { s.status.LastErrMsg = "this model is not supported by your version of Ollama. You may need to upgrade" } - s.done <- fmt.Errorf(s.status.LastErrMsg) + s.done <- errors.New(s.status.LastErrMsg) } else { s.done <- err } diff --git a/main.go b/main.go index 158f0063c..650e03a63 100644 --- a/main.go +++ b/main.go @@ -3,8 +3,9 @@ package main import ( "context" - "github.com/ollama/ollama/cmd" "github.com/spf13/cobra" + + "github.com/ollama/ollama/cmd" ) func main() { diff --git a/openai/openai.go b/openai/openai.go index e66d94165..bda42b4da 100644 --- a/openai/openai.go +++ b/openai/openai.go @@ -5,6 +5,7 @@ import ( "bytes" "encoding/base64" "encoding/json" + "errors" "fmt" "io" "log/slog" @@ -14,6 +15,7 @@ import ( "time" "github.com/gin-gonic/gin" + "github.com/ollama/ollama/api" "github.com/ollama/ollama/types/model" ) @@ -367,24 +369,24 @@ func fromChatRequest(r ChatCompletionRequest) (*api.ChatRequest, error) { for _, c := range content { data, ok := c.(map[string]any) if !ok { - return nil, fmt.Errorf("invalid message format") + return nil, errors.New("invalid message format") } switch data["type"] { case "text": text, ok := data["text"].(string) if !ok { - return nil, fmt.Errorf("invalid message format") + return nil, errors.New("invalid message format") } messages = append(messages, api.Message{Role: msg.Role, Content: text}) case "image_url": var url string if urlMap, ok := data["image_url"].(map[string]any); ok { if url, ok = urlMap["url"].(string); !ok { - return nil, fmt.Errorf("invalid message format") + return nil, errors.New("invalid message format") } } else { if url, ok = data["image_url"].(string); !ok { - return nil, fmt.Errorf("invalid message format") + return nil, errors.New("invalid message format") } } @@ -400,17 +402,17 @@ func fromChatRequest(r ChatCompletionRequest) (*api.ChatRequest, error) { } if !valid { - return nil, fmt.Errorf("invalid image input") + return nil, errors.New("invalid image input") } img, err := base64.StdEncoding.DecodeString(url) if err != nil { - return nil, fmt.Errorf("invalid message format") + return nil, errors.New("invalid message format") } messages = append(messages, api.Message{Role: msg.Role, Images: []api.ImageData{img}}) default: - return nil, fmt.Errorf("invalid message format") + return nil, errors.New("invalid message format") } } default: @@ -423,7 +425,7 @@ func fromChatRequest(r ChatCompletionRequest) (*api.ChatRequest, error) { toolCalls[i].Function.Name = tc.Function.Name err := json.Unmarshal([]byte(tc.Function.Arguments), &toolCalls[i].Function.Arguments) if err != nil { - return nil, fmt.Errorf("invalid tool call arguments") + return nil, errors.New("invalid tool call arguments") } } messages = append(messages, api.Message{Role: msg.Role, ToolCalls: toolCalls}) @@ -737,14 +739,12 @@ func (w *RetrieveWriter) Write(data []byte) (int, error) { func (w *EmbedWriter) writeResponse(data []byte) (int, error) { var embedResponse api.EmbedResponse err := json.Unmarshal(data, &embedResponse) - if err != nil { return 0, err } w.ResponseWriter.Header().Set("Content-Type", "application/json") err = json.NewEncoder(w.ResponseWriter).Encode(toEmbeddingList(w.model, embedResponse)) - if err != nil { return 0, err } diff --git a/openai/openai_test.go b/openai/openai_test.go index f978d46c9..e08a96c92 100644 --- a/openai/openai_test.go +++ b/openai/openai_test.go @@ -12,13 +12,16 @@ import ( "time" "github.com/gin-gonic/gin" - "github.com/ollama/ollama/api" "github.com/stretchr/testify/assert" + + "github.com/ollama/ollama/api" ) -const prefix = `data:image/jpeg;base64,` -const image = `iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk+A8AAQUBAScY42YAAAAASUVORK5CYII=` -const imageURL = prefix + image +const ( + prefix = `data:image/jpeg;base64,` + image = `iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk+A8AAQUBAScY42YAAAAASUVORK5CYII=` + imageURL = prefix + image +) func prepareRequest(req *http.Request, body any) { bodyBytes, _ := json.Marshal(body) diff --git a/parser/parser_test.go b/parser/parser_test.go index 48044bc0e..ebd8a7ff5 100644 --- a/parser/parser_test.go +++ b/parser/parser_test.go @@ -82,7 +82,7 @@ TEMPLATE """ {{ if .System }}<|start_header_id|>system<|end_header_id|> } func TestParseFileFrom(t *testing.T) { - var cases = []struct { + cases := []struct { input string expected []Command err error @@ -185,7 +185,7 @@ BADCOMMAND param1 value1 } func TestParseFileMessages(t *testing.T) { - var cases = []struct { + cases := []struct { input string expected []Command err error @@ -276,7 +276,7 @@ MESSAGE system`, } func TestParseFileQuoted(t *testing.T) { - var cases = []struct { + cases := []struct { multiline string expected []Command err error @@ -430,7 +430,7 @@ TEMPLATE """ } func TestParseFileParameters(t *testing.T) { - var cases = map[string]struct { + cases := map[string]struct { name, value string }{ "numa true": {"numa", "true"}, @@ -491,7 +491,7 @@ func TestParseFileParameters(t *testing.T) { } func TestParseFileComments(t *testing.T) { - var cases = []struct { + cases := []struct { input string expected []Command }{ @@ -516,7 +516,7 @@ FROM foo } func TestParseFileFormatParseFile(t *testing.T) { - var cases = []string{ + cases := []string{ ` FROM foo ADAPTER adapter1 diff --git a/progress/bar.go b/progress/bar.go index 476ea8881..410b6e23f 100644 --- a/progress/bar.go +++ b/progress/bar.go @@ -6,8 +6,9 @@ import ( "strings" "time" - "github.com/ollama/ollama/format" "golang.org/x/term" + + "github.com/ollama/ollama/format" ) type Bar struct { diff --git a/readline/buffer.go b/readline/buffer.go index b7cf9b13f..68573d400 100644 --- a/readline/buffer.go +++ b/readline/buffer.go @@ -13,7 +13,7 @@ type Buffer struct { DisplayPos int Pos int Buf *arraylist.List - //LineHasSpace is an arraylist of bools to keep track of whether a line has a space at the end + // LineHasSpace is an arraylist of bools to keep track of whether a line has a space at the end LineHasSpace *arraylist.List Prompt *Prompt LineWidth int @@ -56,7 +56,7 @@ func (b *Buffer) GetLineSpacing(line int) bool { func (b *Buffer) MoveLeft() { if b.Pos > 0 { - //asserts that we retrieve a rune + // asserts that we retrieve a rune if e, ok := b.Buf.Get(b.Pos - 1); ok { if r, ok := e.(rune); ok { rLength := runewidth.RuneWidth(r) diff --git a/readline/errors.go b/readline/errors.go index 40e40cb77..bb3fbd473 100644 --- a/readline/errors.go +++ b/readline/errors.go @@ -4,9 +4,7 @@ import ( "errors" ) -var ( - ErrInterrupt = errors.New("Interrupt") -) +var ErrInterrupt = errors.New("Interrupt") type InterruptError struct { Line []rune diff --git a/readline/term_linux.go b/readline/term_linux.go index e9ed07451..e9e36da99 100644 --- a/readline/term_linux.go +++ b/readline/term_linux.go @@ -7,8 +7,10 @@ import ( "unsafe" ) -const tcgets = 0x5401 -const tcsets = 0x5402 +const ( + tcgets = 0x5401 + tcsets = 0x5402 +) func getTermios(fd uintptr) (*Termios, error) { termios := new(Termios) diff --git a/server/download.go b/server/download.go index 10074554a..a903d96fc 100644 --- a/server/download.go +++ b/server/download.go @@ -28,8 +28,10 @@ import ( const maxRetries = 6 -var errMaxRetriesExceeded = errors.New("max retries exceeded") -var errPartStalled = errors.New("part stalled") +var ( + errMaxRetriesExceeded = errors.New("max retries exceeded") + errPartStalled = errors.New("part stalled") +) var blobDownloadManager sync.Map diff --git a/server/images.go b/server/images.go index 5f3eee88b..81357f3c0 100644 --- a/server/images.go +++ b/server/images.go @@ -828,7 +828,7 @@ func PushModel(ctx context.Context, name string, regOpts *registryOptions, fn fu fn(api.ProgressResponse{Status: "retrieving manifest"}) if mp.ProtocolScheme == "http" && !regOpts.Insecure { - return fmt.Errorf("insecure protocol http") + return errors.New("insecure protocol http") } manifest, _, err := GetManifest(mp) @@ -895,7 +895,7 @@ func PullModel(ctx context.Context, name string, regOpts *registryOptions, fn fu } if mp.ProtocolScheme == "http" && !regOpts.Insecure { - return fmt.Errorf("insecure protocol http") + return errors.New("insecure protocol http") } fn(api.ProgressResponse{Status: "pulling manifest"}) @@ -1010,7 +1010,7 @@ func GetSHA256Digest(r io.Reader) (string, int64) { return fmt.Sprintf("sha256:%x", h.Sum(nil)), n } -var errUnauthorized = fmt.Errorf("unauthorized: access denied") +var errUnauthorized = errors.New("unauthorized: access denied") // getTokenSubject returns the subject of a JWT token, it does not validate the token func getTokenSubject(token string) string { diff --git a/server/manifest.go b/server/manifest.go index 726bb48d8..b8df11efb 100644 --- a/server/manifest.go +++ b/server/manifest.go @@ -2,9 +2,9 @@ package server import ( "crypto/sha256" + "encoding/hex" "encoding/json" "errors" - "fmt" "io" "log/slog" "os" @@ -88,7 +88,7 @@ func ParseNamedManifest(n model.Name) (*Manifest, error) { m.filepath = p m.fi = fi - m.digest = fmt.Sprintf("%x", sha256sum.Sum(nil)) + m.digest = hex.EncodeToString(sha256sum.Sum(nil)) return &m, nil } diff --git a/server/manifest_test.go b/server/manifest_test.go index a4af5d5e0..70ab7fa2f 100644 --- a/server/manifest_test.go +++ b/server/manifest_test.go @@ -14,7 +14,7 @@ func createManifest(t *testing.T, path, name string) { t.Helper() p := filepath.Join(path, "manifests", name) - if err := os.MkdirAll(filepath.Dir(p), 0755); err != nil { + if err := os.MkdirAll(filepath.Dir(p), 0o755); err != nil { t.Fatal(err) } diff --git a/server/model_test.go b/server/model_test.go index 0a2225d5b..aa214d3d3 100644 --- a/server/model_test.go +++ b/server/model_test.go @@ -9,6 +9,7 @@ import ( "testing" "github.com/google/go-cmp/cmp" + "github.com/ollama/ollama/api" "github.com/ollama/ollama/template" ) diff --git a/server/prompt_test.go b/server/prompt_test.go index 02d23785f..5fe3d4c56 100644 --- a/server/prompt_test.go +++ b/server/prompt_test.go @@ -6,6 +6,7 @@ import ( "testing" "github.com/google/go-cmp/cmp" + "github.com/ollama/ollama/api" "github.com/ollama/ollama/template" ) diff --git a/server/routes.go b/server/routes.go index a745fb20f..b9c66b650 100644 --- a/server/routes.go +++ b/server/routes.go @@ -55,8 +55,10 @@ func init() { gin.SetMode(mode) } -var errRequired = errors.New("is required") -var errBadTemplate = errors.New("template error") +var ( + errRequired = errors.New("is required") + errBadTemplate = errors.New("template error") +) func modelOptions(model *Model, requestOpts map[string]interface{}) (api.Options, error) { opts := api.DefaultOptions() @@ -369,7 +371,6 @@ func (s *Server) EmbedHandler(c *gin.Context) { input[i] = s } embeddings, err := r.Embed(c.Request.Context(), input) - if err != nil { slog.Error("embedding generation failed", "error", err) c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to generate embedding"}) @@ -430,7 +431,6 @@ func (s *Server) EmbeddingsHandler(c *gin.Context) { } embeddings, err := r.Embed(c.Request.Context(), []string{req.Prompt}) - if err != nil { slog.Info(fmt.Sprintf("embedding generation failed: %v", err)) c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to generate embedding"}) @@ -556,7 +556,7 @@ func checkNameExists(name model.Name) error { for n := range names { if strings.EqualFold(n.Filepath(), name.Filepath()) && n != name { - return fmt.Errorf("a model with that name already exists") + return errors.New("a model with that name already exists") } } @@ -729,7 +729,7 @@ func GetModelInfo(req api.ShowRequest) (*api.ShowResponse, error) { n := model.ParseName(req.Model) if !n.IsValid() { - return nil, fmt.Errorf("invalid model name") + return nil, errors.New("invalid model name") } manifest, err := ParseNamedManifest(n) @@ -993,7 +993,7 @@ func allowedHost(host string) bool { return true } - var tlds = []string{ + tlds := []string{ "localhost", "local", "internal", diff --git a/server/routes_create_test.go b/server/routes_create_test.go index 9b7009df9..9fd7f8cd4 100644 --- a/server/routes_create_test.go +++ b/server/routes_create_test.go @@ -13,6 +13,7 @@ import ( "testing" "github.com/gin-gonic/gin" + "github.com/ollama/ollama/api" "github.com/ollama/ollama/llm" ) @@ -489,7 +490,7 @@ func TestCreateTemplateSystem(t *testing.T) { Modelfile: fmt.Sprintf("FROM %s\nTEMPLATE {{ .Prompt", createBinFile(t, nil, nil)), Stream: &stream, }) - + if w.Code != http.StatusBadRequest { t.Fatalf("expected status code 400, actual %d", w.Code) } @@ -501,7 +502,7 @@ func TestCreateTemplateSystem(t *testing.T) { Modelfile: fmt.Sprintf("FROM %s\nTEMPLATE {{ if .Prompt }}", createBinFile(t, nil, nil)), Stream: &stream, }) - + if w.Code != http.StatusBadRequest { t.Fatalf("expected status code 400, actual %d", w.Code) } @@ -513,7 +514,7 @@ func TestCreateTemplateSystem(t *testing.T) { Modelfile: fmt.Sprintf("FROM %s\nTEMPLATE {{ Prompt }}", createBinFile(t, nil, nil)), Stream: &stream, }) - + if w.Code != http.StatusBadRequest { t.Fatalf("expected status code 400, actual %d", w.Code) } diff --git a/server/routes_delete_test.go b/server/routes_delete_test.go index 2354d730a..1c950d669 100644 --- a/server/routes_delete_test.go +++ b/server/routes_delete_test.go @@ -9,6 +9,7 @@ import ( "testing" "github.com/gin-gonic/gin" + "github.com/ollama/ollama/api" "github.com/ollama/ollama/types/model" ) diff --git a/server/routes_list_test.go b/server/routes_list_test.go index 29e3214c5..6e92b7a1a 100644 --- a/server/routes_list_test.go +++ b/server/routes_list_test.go @@ -8,6 +8,7 @@ import ( "testing" "github.com/gin-gonic/gin" + "github.com/ollama/ollama/api" ) diff --git a/server/routes_test.go b/server/routes_test.go index 17da23054..ef7248ef7 100644 --- a/server/routes_test.go +++ b/server/routes_test.go @@ -333,7 +333,6 @@ func Test_Routes(t *testing.T) { t.Fatalf("expected content type application/json; charset=utf-8, got %s", contentType) } _, err := io.ReadAll(resp.Body) - if err != nil { t.Fatal(err) } diff --git a/server/sched.go b/server/sched.go index 700642c63..c378865b0 100644 --- a/server/sched.go +++ b/server/sched.go @@ -58,7 +58,7 @@ var defaultModelsPerGPU = 3 // we'll back off down to 1 to try to get it to fit var defaultParallel = 4 -var ErrMaxQueue = fmt.Errorf("server busy, please try again. maximum pending requests exceeded") +var ErrMaxQueue = errors.New("server busy, please try again. maximum pending requests exceeded") func InitScheduler(ctx context.Context) *Scheduler { maxQueue := envconfig.MaxQueue() diff --git a/server/sched_test.go b/server/sched_test.go index 80395714b..c87174309 100644 --- a/server/sched_test.go +++ b/server/sched_test.go @@ -3,23 +3,25 @@ package server import ( "bytes" "context" - "fmt" + "errors" "log/slog" "os" "testing" "time" + "github.com/stretchr/testify/require" + "github.com/ollama/ollama/api" "github.com/ollama/ollama/app/lifecycle" "github.com/ollama/ollama/format" "github.com/ollama/ollama/gpu" "github.com/ollama/ollama/llm" - "github.com/stretchr/testify/require" ) -func init() { +func TestMain(m *testing.M) { os.Setenv("OLLAMA_DEBUG", "1") lifecycle.InitLogging() + os.Exit(m.Run()) } func TestInitScheduler(t *testing.T) { @@ -46,7 +48,7 @@ func TestLoad(t *testing.T) { } // Fail to load model first s.newServerFn = func(gpus gpu.GpuInfoList, model string, ggml *llm.GGML, adapters []string, projectors []string, opts api.Options, numParallel int) (llm.LlamaServer, error) { - return nil, fmt.Errorf("something failed to load model blah") + return nil, errors.New("something failed to load model blah") } gpus := gpu.GpuInfoList{} s.load(req, ggml, gpus, 0) @@ -75,7 +77,7 @@ func TestLoad(t *testing.T) { } req.model.ModelPath = "dummy_model_path" - server.waitResp = fmt.Errorf("wait failure") + server.waitResp = errors.New("wait failure") s.load(req, ggml, gpus, 0) select { case err := <-req.errCh: @@ -600,7 +602,7 @@ func TestNeedsReload(t *testing.T) { resp = runner.needsReload(ctx, req) require.True(t, resp) req.opts.NumBatch = runner.Options.NumBatch - llm.pingResp = fmt.Errorf("foo") + llm.pingResp = errors.New("foo") resp = runner.needsReload(ctx, req) require.True(t, resp) llm.pingResp = nil @@ -724,15 +726,19 @@ func (s *mockLlm) WaitUntilRunning(ctx context.Context) error { return s.waitRes func (s *mockLlm) Completion(ctx context.Context, req llm.CompletionRequest, fn func(llm.CompletionResponse)) error { return s.completionResp } + func (s *mockLlm) Embed(ctx context.Context, input []string) (*llm.EmbedResponse, error) { return s.embedResp, s.embedRespErr } + func (s *mockLlm) Tokenize(ctx context.Context, content string) ([]int, error) { return s.tokenizeResp, s.tokenizeRespErr } + func (s *mockLlm) Detokenize(ctx context.Context, tokens []int) (string, error) { return s.detokenizeResp, s.detonekizeRespErr } + func (s *mockLlm) Close() error { s.closeCalled = true return s.closeResp diff --git a/server/upload.go b/server/upload.go index c4078c22b..b5a244ea8 100644 --- a/server/upload.go +++ b/server/upload.go @@ -12,13 +12,15 @@ import ( "net/http" "net/url" "os" + "strconv" "sync" "sync/atomic" "time" + "golang.org/x/sync/errgroup" + "github.com/ollama/ollama/api" "github.com/ollama/ollama/format" - "golang.org/x/sync/errgroup" ) var blobUploadManager sync.Map @@ -212,7 +214,7 @@ func (b *blobUpload) Run(ctx context.Context, opts *registryOptions) { func (b *blobUpload) uploadPart(ctx context.Context, method string, requestURL *url.URL, part *blobUploadPart, opts *registryOptions) error { headers := make(http.Header) headers.Set("Content-Type", "application/octet-stream") - headers.Set("Content-Length", fmt.Sprintf("%d", part.Size)) + headers.Set("Content-Length", strconv.FormatInt(part.Size, 10)) if method == http.MethodPatch { headers.Set("X-Redirect-Uploads", "1") diff --git a/template/template.go b/template/template.go index 3e0afcd1d..5dc484f47 100644 --- a/template/template.go +++ b/template/template.go @@ -15,8 +15,9 @@ import ( "text/template/parse" "github.com/agnivade/levenshtein" - "github.com/ollama/ollama/api" "golang.org/x/exp/maps" + + "github.com/ollama/ollama/api" ) //go:embed index.json diff --git a/template/template_test.go b/template/template_test.go index b46e1df5b..113e06838 100644 --- a/template/template_test.go +++ b/template/template_test.go @@ -12,6 +12,7 @@ import ( "testing" "github.com/google/go-cmp/cmp" + "github.com/ollama/ollama/api" "github.com/ollama/ollama/llm" ) diff --git a/types/errtypes/errtypes.go b/types/errtypes/errtypes.go index d30731140..27c3f913e 100644 --- a/types/errtypes/errtypes.go +++ b/types/errtypes/errtypes.go @@ -6,8 +6,10 @@ import ( "strings" ) -const UnknownOllamaKeyErrMsg = "unknown ollama key" -const InvalidModelNameErrMsg = "invalid model name" +const ( + UnknownOllamaKeyErrMsg = "unknown ollama key" + InvalidModelNameErrMsg = "invalid model name" +) // TODO: This should have a structured response from the API type UnknownOllamaKey struct { diff --git a/types/model/name.go b/types/model/name.go index 5e475687e..018cb2f5f 100644 --- a/types/model/name.go +++ b/types/model/name.go @@ -258,7 +258,7 @@ func (n Name) IsValid() bool { // IsFullyQualified returns true if all parts of the name are present and // valid without the digest. func (n Name) IsFullyQualified() bool { - var parts = []string{ + parts := []string{ n.Host, n.Namespace, n.Model, From df3802a65fa5876708fa03e0369248bd0ea4b2c4 Mon Sep 17 00:00:00 2001 From: Daniel Hiltgen Date: Thu, 1 Aug 2024 17:22:25 -0700 Subject: [PATCH 198/384] Adjust arm cuda repo paths Ubuntu distros fail to install cuda drivers since aarch64 isn't valid --- scripts/install.sh | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/scripts/install.sh b/scripts/install.sh index aa8b3e5e3..03af5a69e 100644 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -209,15 +209,15 @@ install_cuda_driver_yum() { case $PACKAGE_MANAGER in yum) $SUDO $PACKAGE_MANAGER -y install yum-utils - if curl -I --silent --fail --location "https://developer.download.nvidia.com/compute/cuda/repos/$1$2/$(uname -m)/cuda-$1$2.repo" >/dev/null ; then - $SUDO $PACKAGE_MANAGER-config-manager --add-repo https://developer.download.nvidia.com/compute/cuda/repos/$1$2/$(uname -m)/cuda-$1$2.repo + if curl -I --silent --fail --location "https://developer.download.nvidia.com/compute/cuda/repos/$1$2/$(uname -m | sed -e 's/aarch64/sbsa/')/cuda-$1$2.repo" >/dev/null ; then + $SUDO $PACKAGE_MANAGER-config-manager --add-repo https://developer.download.nvidia.com/compute/cuda/repos/$1$2/$(uname -m | sed -e 's/aarch64/sbsa/')/cuda-$1$2.repo else error $CUDA_REPO_ERR_MSG fi ;; dnf) - if curl -I --silent --fail --location "https://developer.download.nvidia.com/compute/cuda/repos/$1$2/$(uname -m)/cuda-$1$2.repo" >/dev/null ; then - $SUDO $PACKAGE_MANAGER config-manager --add-repo https://developer.download.nvidia.com/compute/cuda/repos/$1$2/$(uname -m)/cuda-$1$2.repo + if curl -I --silent --fail --location "https://developer.download.nvidia.com/compute/cuda/repos/$1$2/$(uname -m | sed -e 's/aarch64/sbsa/')/cuda-$1$2.repo" >/dev/null ; then + $SUDO $PACKAGE_MANAGER config-manager --add-repo https://developer.download.nvidia.com/compute/cuda/repos/$1$2/$(uname -m | sed -e 's/aarch64/sbsa/')/cuda-$1$2.repo else error $CUDA_REPO_ERR_MSG fi @@ -245,8 +245,8 @@ install_cuda_driver_yum() { # ref: https://docs.nvidia.com/cuda/cuda-installation-guide-linux/index.html#debian install_cuda_driver_apt() { status 'Installing NVIDIA repository...' - if curl -I --silent --fail --location "https://developer.download.nvidia.com/compute/cuda/repos/$1$2/$(uname -m)/cuda-keyring_1.1-1_all.deb" >/dev/null ; then - curl -fsSL -o $TEMP_DIR/cuda-keyring.deb https://developer.download.nvidia.com/compute/cuda/repos/$1$2/$(uname -m)/cuda-keyring_1.1-1_all.deb + if curl -I --silent --fail --location "https://developer.download.nvidia.com/compute/cuda/repos/$1$2/$(uname -m | sed -e 's/aarch64/sbsa/')/cuda-keyring_1.1-1_all.deb" >/dev/null ; then + curl -fsSL -o $TEMP_DIR/cuda-keyring.deb https://developer.download.nvidia.com/compute/cuda/repos/$1$2/$(uname -m | sed -e 's/aarch64/sbsa/')/cuda-keyring_1.1-1_all.deb else error $CUDA_REPO_ERR_MSG fi From 85c7f11170cc5aeafdebd7456381b1b3673f1fec Mon Sep 17 00:00:00 2001 From: royjhan <65097070+royjhan@users.noreply.github.com> Date: Fri, 2 Aug 2024 16:05:57 -0400 Subject: [PATCH 199/384] Update docs (#5310) --- docs/openai.md | 68 +++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 64 insertions(+), 4 deletions(-) diff --git a/docs/openai.md b/docs/openai.md index b4443cb02..a8728e0e2 100644 --- a/docs/openai.md +++ b/docs/openai.md @@ -28,6 +28,22 @@ chat_completion = client.chat.completions.create( model='llama3', ) +response = client.chat.completions.create( + model="llava", + messages=[ + { + "role": "user", + "content": [ + {"type": "text", "text": "What's in this image?"}, + { + "type": "image_url", + "image_url": "iVBORw0KGgoAAAANSUhEUgAAAG0AAABmCAYAAADBPx+VAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAA3VSURBVHgB7Z27r0zdG8fX743i1bi1ikMoFMQloXRpKFFIqI7LH4BEQ+NWIkjQuSWCRIEoULk0gsK1kCBI0IhrQVT7tz/7zZo888yz1r7MnDl7z5xvsjkzs2fP3uu71nNfa7lkAsm7d++Sffv2JbNmzUqcc8m0adOSzZs3Z+/XES4ZckAWJEGWPiCxjsQNLWmQsWjRIpMseaxcuTKpG/7HP27I8P79e7dq1ars/yL4/v27S0ejqwv+cUOGEGGpKHR37tzJCEpHV9tnT58+dXXCJDdECBE2Ojrqjh071hpNECjx4cMHVycM1Uhbv359B2F79+51586daxN/+pyRkRFXKyRDAqxEp4yMlDDzXG1NPnnyJKkThoK0VFd1ELZu3TrzXKxKfW7dMBQ6bcuWLW2v0VlHjx41z717927ba22U9APcw7Nnz1oGEPeL3m3p2mTAYYnFmMOMXybPPXv2bNIPpFZr1NHn4HMw0KRBjg9NuRw95s8PEcz/6DZELQd/09C9QGq5RsmSRybqkwHGjh07OsJSsYYm3ijPpyHzoiacg35MLdDSIS/O1yM778jOTwYUkKNHWUzUWaOsylE00MyI0fcnOwIdjvtNdW/HZwNLGg+sR1kMepSNJXmIwxBZiG8tDTpEZzKg0GItNsosY8USkxDhD0Rinuiko2gfL/RbiD2LZAjU9zKQJj8RDR0vJBR1/Phx9+PHj9Z7REF4nTZkxzX4LCXHrV271qXkBAPGfP/atWvu/PnzHe4C97F48eIsRLZ9+3a3f/9+87dwP1JxaF7/3r17ba+5l4EcaVo0lj3SBq5kGTJSQmLWMjgYNei2GPT1MuMqGTDEFHzeQSP2wi/jGnkmPJ/nhccs44jvDAxpVcxnq0F6eT8h4ni/iIWpR5lPyA6ETkNXoSukvpJAD3AsXLiwpZs49+fPn5ke4j10TqYvegSfn0OnafC+Tv9ooA/JPkgQysqQNBzagXY55nO/oa1F7qvIPWkRL12WRpMWUvpVDYmxAPehxWSe8ZEXL20sadYIozfmNch4QJPAfeJgW3rNsnzphBKNJM2KKODo1rVOMRYik5ETy3ix4qWNI81qAAirizgMIc+yhTytx0JWZuNI03qsrgWlGtwjoS9XwgUhWGyhUaRZZQNNIEwCiXD16tXcAHUs79co0vSD8rrJCIW98pzvxpAWyyo3HYwqS0+H0BjStClcZJT5coMm6D2LOF8TolGJtK9fvyZpyiC5ePFi9nc/oJU4eiEP0jVoAnHa9wyJycITMP78+eMeP37sXrx44d6+fdt6f82aNdkx1pg9e3Zb5W+RSRE+n+VjksQWifvVaTKFhn5O8my63K8Qabdv33b379/PiAP//vuvW7BggZszZ072/+TJk91YgkafPn166zXB1rQHFvouAWHq9z3SEevSUerqCn2/dDCeta2jxYbr69evk4MHDyY7d+7MjhMnTiTPnz9Pfv/+nfQT2ggpO2dMF8cghuoM7Ygj5iWCqRlGFml0QC/ftGmTmzt3rmsaKDsgBSPh0/8yPeLLBihLkOKJc0jp8H8vUzcxIA1k6QJ/c78tWEyj5P3o4u9+jywNPdJi5rAH9x0KHcl4Hg570eQp3+vHXGyrmEeigzQsQsjavXt38ujRo44LQuDDhw+TW7duRS1HGgMxhNXHgflaNTOsHyKvHK5Ijo2jbFjJBQK9YwFd6RVMzfgRBmEfP37suBBm/p49e1qjEP2mwTViNRo0VJWH1deMXcNK08uUjVUu7s/zRaL+oLNxz1bpANco4npUgX4G2eFbpDFyQoQxojBCpEGSytmOH8qrH5Q9vuzD6ofQylkCUmh8DBAr+q8JCyVNtWQIidKQE9wNtLSQnS4jDSsxNHogzFuQBw4cyM61UKVsjfr3ooBkPSqqQHesUPWVtzi9/vQi1T+rJj7WiTz4Pt/l3LxUkr5P2VYZaZ4URpsE+st/dujQoaBBYokbrz/8TJNQYLSonrPS9kUaSkPeZyj1AWSj+d+VBoy1pIWVNed8P0Ll/ee5HdGRhrHhR5GGN0r4LGZBaj8oFDJitBTJzIZgFcmU0Y8ytWMZMzJOaXUSrUs5RxKnrxmbb5YXO9VGUhtpXldhEUogFr3IzIsvlpmdosVcGVGXFWp2oU9kLFL3dEkSz6NHEY1sjSRdIuDFWEhd8KxFqsRi1uM/nz9/zpxnwlESONdg6dKlbsaMGS4EHFHtjFIDHwKOo46l4TxSuxgDzi+rE2jg+BaFruOX4HXa0Nnf1lwAPufZeF8/r6zD97WK2qFnGjBxTw5qNGPxT+5T/r7/7RawFC3j4vTp09koCxkeHjqbHJqArmH5UrFKKksnxrK7FuRIs8STfBZv+luugXZ2pR/pP9Ois4z+TiMzUUkUjD0iEi1fzX8GmXyuxUBRcaUfykV0YZnlJGKQpOiGB76x5GeWkWWJc3mOrK6S7xdND+W5N6XyaRgtWJFe13GkaZnKOsYqGdOVVVbGupsyA/l7emTLHi7vwTdirNEt0qxnzAvBFcnQF16xh/TMpUuXHDowhlA9vQVraQhkudRdzOnK+04ZSP3DUhVSP61YsaLtd/ks7ZgtPcXqPqEafHkdqa84X6aCeL7YWlv6edGFHb+ZFICPlljHhg0bKuk0CSvVznWsotRu433alNdFrqG45ejoaPCaUkWERpLXjzFL2Rpllp7PJU2a/v7Ab8N05/9t27Z16KUqoFGsxnI9EosS2niSYg9SpU6B4JgTrvVW1flt1sT+0ADIJU2maXzcUTraGCRaL1Wp9rUMk16PMom8QhruxzvZIegJjFU7LLCePfS8uaQdPny4jTTL0dbee5mYokQsXTIWNY46kuMbnt8Kmec+LGWtOVIl9cT1rCB0V8WqkjAsRwta93TbwNYoGKsUSChN44lgBNCoHLHzquYKrU6qZ8lolCIN0Rh6cP0Q3U6I6IXILYOQI513hJaSKAorFpuHXJNfVlpRtmYBk1Su1obZr5dnKAO+L10Hrj3WZW+E3qh6IszE37F6EB+68mGpvKm4eb9bFrlzrok7fvr0Kfv727dvWRmdVTJHw0qiiCUSZ6wCK+7XL/AcsgNyL74DQQ730sv78Su7+t/A36MdY0sW5o40ahslXr58aZ5HtZB8GH64m9EmMZ7FpYw4T6QnrZfgenrhFxaSiSGXtPnz57e9TkNZLvTjeqhr734CNtrK41L40sUQckmj1lGKQ0rC37x544r8eNXRpnVE3ZZY7zXo8NomiO0ZUCj2uHz58rbXoZ6gc0uA+F6ZeKS/jhRDUq8MKrTho9fEkihMmhxtBI1DxKFY9XLpVcSkfoi8JGnToZO5sU5aiDQIW716ddt7ZLYtMQlhECdBGXZZMWldY5BHm5xgAroWj4C0hbYkSc/jBmggIrXJWlZM6pSETsEPGqZOndr2uuuR5rF169a2HoHPdurUKZM4CO1WTPqaDaAd+GFGKdIQkxAn9RuEWcTRyN2KSUgiSgF5aWzPTeA/lN5rZubMmR2bE4SIC4nJoltgAV/dVefZm72AtctUCJU2CMJ327hxY9t7EHbkyJFseq+EJSY16RPo3Dkq1kkr7+q0bNmyDuLQcZBEPYmHVdOBiJyIlrRDq41YPWfXOxUysi5fvtyaj+2BpcnsUV/oSoEMOk2CQGlr4ckhBwaetBhjCwH0ZHtJROPJkyc7UjcYLDjmrH7ADTEBXFfOYmB0k9oYBOjJ8b4aOYSe7QkKcYhFlq3QYLQhSidNmtS2RATwy8YOM3EQJsUjKiaWZ+vZToUQgzhkHXudb/PW5YMHD9yZM2faPsMwoc7RciYJXbGuBqJ1UIGKKLv915jsvgtJxCZDubdXr165mzdvtr1Hz5LONA8jrUwKPqsmVesKa49S3Q4WxmRPUEYdTjgiUcfUwLx589ySJUva3oMkP6IYddq6HMS4o55xBJBUeRjzfa4Zdeg56QZ43LhxoyPo7Lf1kNt7oO8wWAbNwaYjIv5lhyS7kRf96dvm5Jah8vfvX3flyhX35cuX6HfzFHOToS1H4BenCaHvO8pr8iDuwoUL7tevX+b5ZdbBair0xkFIlFDlW4ZknEClsp/TzXyAKVOmmHWFVSbDNw1l1+4f90U6IY/q4V27dpnE9bJ+v87QEydjqx/UamVVPRG+mwkNTYN+9tjkwzEx+atCm/X9WvWtDtAb68Wy9LXa1UmvCDDIpPkyOQ5ZwSzJ4jMrvFcr0rSjOUh+GcT4LSg5ugkW1Io0/SCDQBojh0hPlaJdah+tkVYrnTZowP8iq1F1TgMBBauufyB33x1v+NWFYmT5KmppgHC+NkAgbmRkpD3yn9QIseXymoTQFGQmIOKTxiZIWpvAatenVqRVXf2nTrAWMsPnKrMZHz6bJq5jvce6QK8J1cQNgKxlJapMPdZSR64/UivS9NztpkVEdKcrs5alhhWP9NeqlfWopzhZScI6QxseegZRGeg5a8C3Re1Mfl1ScP36ddcUaMuv24iOJtz7sbUjTS4qBvKmstYJoUauiuD3k5qhyr7QdUHMeCgLa1Ear9NquemdXgmum4fvJ6w1lqsuDhNrg1qSpleJK7K3TF0Q2jSd94uSZ60kK1e3qyVpQK6PVWXp2/FC3mp6jBhKKOiY2h3gtUV64TWM6wDETRPLDfSakXmH3w8g9Jlug8ZtTt4kVF0kLUYYmCCtD/DrQ5YhMGbA9L3ucdjh0y8kOHW5gU/VEEmJTcL4Pz/f7mgoAbYkAAAAAElFTkSuQmCC", + }, + ], + } + ], + max_tokens=300, + list_completion = client.models.list() model = client.models.retrieve("llama3") @@ -51,8 +67,24 @@ const openai = new OpenAI({ }) const chatCompletion = await openai.chat.completions.create({ - messages: [{ role: 'user', content: 'Say this is a test' }], - model: 'llama3', + messages: [{ role: 'user', content: 'Say this is a test' }], + model: 'llama3', +}) + +const response = await openai.chat.completions.create({ + model: "llava", + messages: [ + { + role: "user", + content: [ + { type: "text", text: "What's in this image?" }, + { + type: "image_url", + image_url: "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", + }, + ], + }, + ], }) const listCompletion = await openai.models.list() @@ -67,7 +99,7 @@ const embedding = await openai.embeddings.create({ ### `curl` -``` +``` shell curl http://localhost:11434/v1/chat/completions \ -H "Content-Type: application/json" \ -d '{ @@ -84,6 +116,30 @@ curl http://localhost:11434/v1/chat/completions \ ] }' +curl http://localhost:11434/v1/chat/completions \ + -H "Content-Type: application/json" \ + -d '{ + "model": "llava", + "messages": [ + { + "role": "user", + "content": [ + { + "type": "text", + "text": "What'\''s in this image?" + }, + { + "type": "image_url", + "image_url": { + "url": "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" + } + } + ] + } + ], + "max_tokens": 300 + }' + curl http://localhost:11434/v1/models curl http://localhost:11434/v1/models/llama3 @@ -106,6 +162,7 @@ curl http://localhost:11434/v1/embeddings \ - [x] Streaming - [x] JSON mode - [x] Reproducible outputs +- [x] Vision - [x] Tools (streaming support coming soon) - [ ] Vision - [ ] Logprobs @@ -115,7 +172,10 @@ curl http://localhost:11434/v1/embeddings \ - [x] `model` - [x] `messages` - [x] Text `content` - - [ ] Array of `content` parts + - [x] Image `content` + - [x] Base64 encoded image + - [ ] Image URL + - [x] Array of `content` parts - [x] `frequency_penalty` - [x] `presence_penalty` - [x] `response_format` From 4addf6b587b38afb9d3038035c6fc792136bc448 Mon Sep 17 00:00:00 2001 From: royjhan <65097070+royjhan@users.noreply.github.com> Date: Fri, 2 Aug 2024 16:16:23 -0400 Subject: [PATCH 200/384] Update OpenAI Compatibility Docs with /v1/completions (#5311) * Update docs * token bug corrected * Update docs/openai.md * Update docs/openai.md * add suffix * merge conflicts * merge conflicts --- docs/openai.md | 57 +++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 54 insertions(+), 3 deletions(-) diff --git a/docs/openai.md b/docs/openai.md index a8728e0e2..7b3a3f315 100644 --- a/docs/openai.md +++ b/docs/openai.md @@ -43,6 +43,12 @@ response = client.chat.completions.create( } ], max_tokens=300, +) + +completion = client.completions.create( + model="llama3", + prompt="Say this is a test", +) list_completion = client.models.list() @@ -50,7 +56,7 @@ model = client.models.retrieve("llama3") embeddings = client.embeddings.create( model="all-minilm", - input=["why is the sky blue?", "why is the grass green?"] + input=["why is the sky blue?", "why is the grass green?"], ) ``` @@ -87,14 +93,19 @@ const response = await openai.chat.completions.create({ ], }) +const completion = await openai.completions.create({ + model: "llama3", + prompt: "Say this is a test.", +}) + const listCompletion = await openai.models.list() -const model = await openai.models.retrieve("llama3"); +const model = await openai.models.retrieve("llama3") const embedding = await openai.embeddings.create({ model: "all-minilm", input: ["why is the sky blue?", "why is the grass green?"], -}); +}) ``` ### `curl` @@ -140,6 +151,13 @@ curl http://localhost:11434/v1/chat/completions \ "max_tokens": 300 }' +curl http://localhost:11434/v1/completions \ + -H "Content-Type: application/json" \ + -d '{ + "model": "llama3", + "prompt": "Say this is a test" + }' + curl http://localhost:11434/v1/models curl http://localhost:11434/v1/models/llama3 @@ -191,6 +209,39 @@ curl http://localhost:11434/v1/embeddings \ - [ ] `user` - [ ] `n` +### `/v1/completions` + +#### Supported features + +- [x] Completions +- [x] Streaming +- [x] JSON mode +- [x] Reproducible outputs +- [ ] Logprobs + +#### Supported request fields + +- [x] `model` +- [x] `prompt` +- [x] `frequency_penalty` +- [x] `presence_penalty` +- [x] `seed` +- [x] `stop` +- [x] `stream` +- [x] `temperature` +- [x] `top_p` +- [x] `max_tokens` +- [x] `suffix` +- [ ] `best_of` +- [ ] `echo` +- [ ] `logit_bias` +- [ ] `user` +- [ ] `n` + +#### Notes + +- `prompt` currently only accepts a string + ### `/v1/models` #### Notes From a091fadfdaa2e4d6a34cf8bbfe4012913367a35a Mon Sep 17 00:00:00 2001 From: Michael Yang Date: Fri, 2 Aug 2024 15:55:34 -0700 Subject: [PATCH 201/384] use testing tempdirs --- server/routes_create_test.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/server/routes_create_test.go b/server/routes_create_test.go index 9fd7f8cd4..4de07b252 100644 --- a/server/routes_create_test.go +++ b/server/routes_create_test.go @@ -2,6 +2,7 @@ package server import ( "bytes" + "cmp" "encoding/json" "fmt" "io" @@ -53,6 +54,8 @@ func (t *responseRecorder) CloseNotify() <-chan bool { func createRequest(t *testing.T, fn func(*gin.Context), body any) *httptest.ResponseRecorder { t.Helper() + // if OLLAMA_MODELS is not set, set it to the temp directory + t.Setenv("OLLAMA_MODELS", cmp.Or(os.Getenv("OLLAMA_MODELS"), t.TempDir())) w := NewRecorder() c, _ := gin.CreateTestContext(w) From 4221e3986736669cc6fee04c185f613a01476163 Mon Sep 17 00:00:00 2001 From: Ivan Charapanau <38184623+av@users.noreply.github.com> Date: Sat, 3 Aug 2024 02:03:46 +0200 Subject: [PATCH 202/384] Reference ollama integration with Harbor (#6147) --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 0cc152662..f4537e229 100644 --- a/README.md +++ b/README.md @@ -300,6 +300,7 @@ See the [API documentation](./docs/api.md) for all endpoints. - [Sidellama](https://github.com/gyopak/sidellama) (browser-based LLM client) - [LLMStack](https://github.com/trypromptly/LLMStack) (No-code multi-agent framework to build LLM agents and workflows) - [BoltAI for Mac](https://boltai.com) (AI Chat Client for Mac) +- [Harbor](https://github.com/av/harbor) (Containerized LLM Toolkit with Ollama as default backend) ### Terminal From 8b920f35a46c6459e0fd48daa38bc80963bf6462 Mon Sep 17 00:00:00 2001 From: sryu1 <95025816+sryu1@users.noreply.github.com> Date: Mon, 5 Aug 2024 00:58:39 +1000 Subject: [PATCH 203/384] Add Gemma 2 2b (#6151) --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index f4537e229..7c606e1c8 100644 --- a/README.md +++ b/README.md @@ -54,6 +54,7 @@ Here are some example models that can be downloaded: | Llama 3.1 | 405B | 231GB | `ollama run llama3.1:405b` | | Phi 3 Mini | 3.8B | 2.3GB | `ollama run phi3` | | Phi 3 Medium | 14B | 7.9GB | `ollama run phi3:medium` | +| Gemma 2 | 2B | 1.6GB | `ollama run gemma2:2b` | | Gemma 2 | 9B | 5.5GB | `ollama run gemma2` | | Gemma 2 | 27B | 16GB | `ollama run gemma2:27b` | | Mistral | 7B | 4.1GB | `ollama run mistral` | From 6a07344786bf06fe27b188e6a3c97c478386808a Mon Sep 17 00:00:00 2001 From: Michael Yang Date: Sun, 4 Aug 2024 17:25:33 -0700 Subject: [PATCH 204/384] line feed --- docs/docker.md | 142 +++++++++++++++++----------------- llm/ext_server/CMakeLists.txt | 24 +++--- 2 files changed, 83 insertions(+), 83 deletions(-) diff --git a/docs/docker.md b/docs/docker.md index a34c3291a..314666b26 100644 --- a/docs/docker.md +++ b/docs/docker.md @@ -1,71 +1,71 @@ -# Ollama Docker image - -### CPU only - -```bash -docker run -d -v ollama:/root/.ollama -p 11434:11434 --name ollama ollama/ollama -``` - -### Nvidia GPU -Install the [NVIDIA Container Toolkit](https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/latest/install-guide.html#installation). - -#### Install with Apt -1. Configure the repository -```bash -curl -fsSL https://nvidia.github.io/libnvidia-container/gpgkey \ - | sudo gpg --dearmor -o /usr/share/keyrings/nvidia-container-toolkit-keyring.gpg -curl -s -L https://nvidia.github.io/libnvidia-container/stable/deb/nvidia-container-toolkit.list \ - | sed 's#deb https://#deb [signed-by=/usr/share/keyrings/nvidia-container-toolkit-keyring.gpg] https://#g' \ - | sudo tee /etc/apt/sources.list.d/nvidia-container-toolkit.list -sudo apt-get update -``` -2. Install the NVIDIA Container Toolkit packages -```bash -sudo apt-get install -y nvidia-container-toolkit -``` - -#### Install with Yum or Dnf -1. Configure the repository - -```bash -curl -s -L https://nvidia.github.io/libnvidia-container/stable/rpm/nvidia-container-toolkit.repo \ - | sudo tee /etc/yum.repos.d/nvidia-container-toolkit.repo -``` - -2. Install the NVIDIA Container Toolkit packages - -```bash -sudo yum install -y nvidia-container-toolkit -``` - -#### Configure Docker to use Nvidia driver -``` -sudo nvidia-ctk runtime configure --runtime=docker -sudo systemctl restart docker -``` - -#### Start the container - -```bash -docker run -d --gpus=all -v ollama:/root/.ollama -p 11434:11434 --name ollama ollama/ollama -``` - -### AMD GPU - -To run Ollama using Docker with AMD GPUs, use the `rocm` tag and the following command: - -``` -docker run -d --device /dev/kfd --device /dev/dri -v ollama:/root/.ollama -p 11434:11434 --name ollama ollama/ollama:rocm -``` - -### Run model locally - -Now you can run a model: - -``` -docker exec -it ollama ollama run llama3.1 -``` - -### Try different models - -More models can be found on the [Ollama library](https://ollama.com/library). +# Ollama Docker image + +### CPU only + +```bash +docker run -d -v ollama:/root/.ollama -p 11434:11434 --name ollama ollama/ollama +``` + +### Nvidia GPU +Install the [NVIDIA Container Toolkit](https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/latest/install-guide.html#installation). + +#### Install with Apt +1. Configure the repository +```bash +curl -fsSL https://nvidia.github.io/libnvidia-container/gpgkey \ + | sudo gpg --dearmor -o /usr/share/keyrings/nvidia-container-toolkit-keyring.gpg +curl -s -L https://nvidia.github.io/libnvidia-container/stable/deb/nvidia-container-toolkit.list \ + | sed 's#deb https://#deb [signed-by=/usr/share/keyrings/nvidia-container-toolkit-keyring.gpg] https://#g' \ + | sudo tee /etc/apt/sources.list.d/nvidia-container-toolkit.list +sudo apt-get update +``` +2. Install the NVIDIA Container Toolkit packages +```bash +sudo apt-get install -y nvidia-container-toolkit +``` + +#### Install with Yum or Dnf +1. Configure the repository + +```bash +curl -s -L https://nvidia.github.io/libnvidia-container/stable/rpm/nvidia-container-toolkit.repo \ + | sudo tee /etc/yum.repos.d/nvidia-container-toolkit.repo +``` + +2. Install the NVIDIA Container Toolkit packages + +```bash +sudo yum install -y nvidia-container-toolkit +``` + +#### Configure Docker to use Nvidia driver +``` +sudo nvidia-ctk runtime configure --runtime=docker +sudo systemctl restart docker +``` + +#### Start the container + +```bash +docker run -d --gpus=all -v ollama:/root/.ollama -p 11434:11434 --name ollama ollama/ollama +``` + +### AMD GPU + +To run Ollama using Docker with AMD GPUs, use the `rocm` tag and the following command: + +``` +docker run -d --device /dev/kfd --device /dev/dri -v ollama:/root/.ollama -p 11434:11434 --name ollama ollama/ollama:rocm +``` + +### Run model locally + +Now you can run a model: + +``` +docker exec -it ollama ollama run llama3.1 +``` + +### Try different models + +More models can be found on the [Ollama library](https://ollama.com/library). diff --git a/llm/ext_server/CMakeLists.txt b/llm/ext_server/CMakeLists.txt index b63f3c0e5..bfc97c63d 100644 --- a/llm/ext_server/CMakeLists.txt +++ b/llm/ext_server/CMakeLists.txt @@ -1,13 +1,13 @@ -set(TARGET ollama_llama_server) -option(LLAMA_SERVER_VERBOSE "Build verbose logging option for Server" ON) -include_directories(${CMAKE_CURRENT_SOURCE_DIR}) -add_executable(${TARGET} server.cpp utils.hpp json.hpp httplib.h) -install(TARGETS ${TARGET} RUNTIME) -target_compile_definitions(${TARGET} PRIVATE - SERVER_VERBOSE=$ -) -target_link_libraries(${TARGET} PRIVATE ggml llama common llava ${CMAKE_THREAD_LIBS_INIT}) -if (WIN32) - TARGET_LINK_LIBRARIES(${TARGET} PRIVATE ws2_32) -endif() +set(TARGET ollama_llama_server) +option(LLAMA_SERVER_VERBOSE "Build verbose logging option for Server" ON) +include_directories(${CMAKE_CURRENT_SOURCE_DIR}) +add_executable(${TARGET} server.cpp utils.hpp json.hpp httplib.h) +install(TARGETS ${TARGET} RUNTIME) +target_compile_definitions(${TARGET} PRIVATE + SERVER_VERBOSE=$ +) +target_link_libraries(${TARGET} PRIVATE ggml llama common llava ${CMAKE_THREAD_LIBS_INIT}) +if (WIN32) + TARGET_LINK_LIBRARIES(${TARGET} PRIVATE ws2_32) +endif() target_compile_features(${TARGET} PRIVATE cxx_std_11) \ No newline at end of file From b73b0940ef4ed4e08ed1372e0da7bc864313d034 Mon Sep 17 00:00:00 2001 From: frob Date: Mon, 5 Aug 2024 06:10:53 +0200 Subject: [PATCH 205/384] Disable paging for journalctl (#6154) Users using `journalctl` to get logs for issue logging sometimes don't realize that paging is causing information to be missed. --- docs/troubleshooting.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md index 484c4b6ce..589061a89 100644 --- a/docs/troubleshooting.md +++ b/docs/troubleshooting.md @@ -9,7 +9,7 @@ cat ~/.ollama/logs/server.log On **Linux** systems with systemd, the logs can be found with this command: ```shell -journalctl -u ollama +journalctl -u ollama --no-pager ``` When you run Ollama in a **container**, the logs go to stdout/stderr in the container: From ed6c8bfe57e4678090b89fc8f6c4e08ce1b01040 Mon Sep 17 00:00:00 2001 From: Michael Yang Date: Mon, 5 Aug 2024 00:02:47 -0700 Subject: [PATCH 206/384] removeall to remove non-empty temp dirs --- gpu/assets.go | 39 +++++++++++++++++++++++---------------- 1 file changed, 23 insertions(+), 16 deletions(-) diff --git a/gpu/assets.go b/gpu/assets.go index a35b6630b..1c33b55bb 100644 --- a/gpu/assets.go +++ b/gpu/assets.go @@ -67,37 +67,44 @@ func PayloadsDir() (string, error) { // Best effort to clean up prior tmpdirs func cleanupTmpDirs() { - dirs, err := filepath.Glob(filepath.Join(os.TempDir(), "ollama*")) + matches, err := filepath.Glob(filepath.Join(os.TempDir(), "ollama*", "ollama.pid")) if err != nil { return } - for _, d := range dirs { - info, err := os.Stat(d) - if err != nil || !info.IsDir() { + + for _, match := range matches { + raw, err := os.ReadFile(match) + if errors.Is(err, os.ErrNotExist) { + slog.Debug("not a ollama runtime directory, skipping", "path", match) continue - } - raw, err := os.ReadFile(filepath.Join(d, "ollama.pid")) - if err != nil { - slog.Warn("failed to read ollama.pid", "path", d, "error", err) - // No pid, ignore this tmpdir + } else if err != nil { + slog.Warn("could not read ollama.pid, skipping", "path", match, "error", err) continue } pid, err := strconv.Atoi(string(raw)) if err != nil { - slog.Warn("failed to parse pid", "path", d, "error", err) + slog.Warn("invalid pid, skipping", "path", match, "error", err) continue } - proc, err := os.FindProcess(pid) - if err == nil && !errors.Is(proc.Signal(syscall.Signal(0)), os.ErrProcessDone) { - slog.Warn("found running ollama", "pid", pid, "path", d) - // Another running ollama, ignore this tmpdir + p, err := os.FindProcess(pid) + if err == nil && !errors.Is(p.Signal(syscall.Signal(0)), os.ErrProcessDone) { + slog.Warn("process still running, skipping", "pid", pid, "path", match) continue } - if err := os.Remove(d); err != nil { - slog.Warn("unable to cleanup stale tmpdir", "path", d, "error", err) + if err := os.Remove(match); err != nil { + slog.Warn("could not cleanup stale pidfile", "path", match, "error", err) + } + + runners := filepath.Join(filepath.Dir(match), "runners") + if err := os.RemoveAll(runners); err != nil { + slog.Warn("could not cleanup stale runners", "path", runners, "error", err) + } + + if err := os.Remove(filepath.Dir(match)); err != nil { + slog.Warn("could not cleanup stale tmpdir", "path", filepath.Dir(match), "error", err) } } } From 43f9d92008bf1aaa2e89ca50c85761540f70c21a Mon Sep 17 00:00:00 2001 From: Michael Yang Date: Mon, 5 Aug 2024 00:34:09 -0700 Subject: [PATCH 207/384] close pid file --- gpu/assets.go | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/gpu/assets.go b/gpu/assets.go index 1c33b55bb..6d62d0dca 100644 --- a/gpu/assets.go +++ b/gpu/assets.go @@ -49,13 +49,9 @@ func PayloadsDir() (string, error) { } // Track our pid so we can clean up orphaned tmpdirs - pidFilePath := filepath.Join(tmpDir, "ollama.pid") - pidFile, err := os.OpenFile(pidFilePath, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, os.ModePerm) - if err != nil { - return "", err - } - if _, err := pidFile.Write([]byte(strconv.Itoa(os.Getpid()))); err != nil { - return "", err + n := filepath.Join(tmpDir, "ollama.pid") + if err := os.WriteFile(n, []byte(strconv.Itoa(os.Getpid())), 0o644); err != nil { + return "", fmt.Errorf("failed to write pid file %s: %w", n, err) } // We create a distinct subdirectory for payloads within the tmpdir From 04210aa6ddf9ec5d5b6101f6e8a12b68d7aadfee Mon Sep 17 00:00:00 2001 From: Daniel Hiltgen Date: Mon, 5 Aug 2024 09:28:07 -0700 Subject: [PATCH 208/384] Catch one more error log --- llm/status.go | 1 + 1 file changed, 1 insertion(+) diff --git a/llm/status.go b/llm/status.go index d9f361155..604fe9e0d 100644 --- a/llm/status.go +++ b/llm/status.go @@ -26,6 +26,7 @@ var errorPrefixes = []string{ "cudaMalloc failed", "\"ERR\"", "error loading model", + "GGML_ASSERT", } func (w *StatusWriter) Write(b []byte) (int, error) { From f457d63400f9859acdfff1853c53af13429acea5 Mon Sep 17 00:00:00 2001 From: Daniel Hiltgen Date: Mon, 5 Aug 2024 12:56:20 -0700 Subject: [PATCH 209/384] Implement linux NUMA detection If the system has multiple numa nodes, enable numa support in llama.cpp If we detect numactl in the path, use that, else use the basic "distribute" mode. --- api/types.go | 2 -- gpu/cpu_common.go | 21 +++++++++++++++++++++ llm/server.go | 10 ++++++++-- 3 files changed, 29 insertions(+), 4 deletions(-) diff --git a/api/types.go b/api/types.go index c25296521..291522a39 100644 --- a/api/types.go +++ b/api/types.go @@ -231,7 +231,6 @@ type Options struct { // Runner options which must be set when the model is loaded into memory type Runner struct { - UseNUMA bool `json:"numa,omitempty"` NumCtx int `json:"num_ctx,omitempty"` NumBatch int `json:"num_batch,omitempty"` NumGPU int `json:"num_gpu,omitempty"` @@ -615,7 +614,6 @@ func DefaultOptions() Options { F16KV: true, UseMLock: false, UseMMap: nil, - UseNUMA: false, }, } } diff --git a/gpu/cpu_common.go b/gpu/cpu_common.go index 63e88f25b..34edcdc5a 100644 --- a/gpu/cpu_common.go +++ b/gpu/cpu_common.go @@ -1,6 +1,11 @@ package gpu import ( + "os" + "path/filepath" + "runtime" + "strings" + "golang.org/x/sys/cpu" ) @@ -14,3 +19,19 @@ func GetCPUCapability() CPUCapability { // else LCD return CPUCapabilityNone } + +func IsNUMA() bool { + if runtime.GOOS != "linux" { + // numa support in llama.cpp is linux only + return false + } + ids := map[string]interface{}{} + packageIds, _ := filepath.Glob("/sys/devices/system/cpu/cpu*/topology/physical_package_id") + for _, packageId := range packageIds { + id, err := os.ReadFile(packageId) + if err == nil { + ids[strings.TrimSpace(string(id))] = struct{}{} + } + } + return len(ids) > 1 +} diff --git a/llm/server.go b/llm/server.go index 7abc3bd72..152b7582f 100644 --- a/llm/server.go +++ b/llm/server.go @@ -256,8 +256,14 @@ func NewLlamaServer(gpus gpu.GpuInfoList, model string, ggml *GGML, adapters, pr params = append(params, "--mlock") } - if opts.UseNUMA { - params = append(params, "--numa") + if gpu.IsNUMA() { + numaMode := "distribute" + if runtime.GOOS == "linux" { + if _, err := exec.LookPath("numactl"); err == nil { + numaMode = "numactl" + } + } + params = append(params, "--numa", numaMode) } params = append(params, "--parallel", strconv.Itoa(numParallel)) From 7ed367419e8fee28c393f1f80edfb5686fddaed6 Mon Sep 17 00:00:00 2001 From: Michael Yang Date: Mon, 5 Aug 2024 16:34:54 -0700 Subject: [PATCH 210/384] fix concurrency test --- integration/concurrency_test.go | 19 +++++++++---------- integration/llm_test.go | 4 ++-- integration/max_queue_test.go | 2 +- integration/utils_test.go | 10 +++++----- 4 files changed, 17 insertions(+), 18 deletions(-) diff --git a/integration/concurrency_test.go b/integration/concurrency_test.go index 81d0b5878..42e9d0749 100644 --- a/integration/concurrency_test.go +++ b/integration/concurrency_test.go @@ -5,6 +5,7 @@ package integration import ( "context" "log/slog" + "os" "strconv" "sync" "testing" @@ -13,7 +14,6 @@ import ( "github.com/stretchr/testify/require" "github.com/ollama/ollama/api" - "github.com/ollama/ollama/envconfig" "github.com/ollama/ollama/format" ) @@ -41,8 +41,8 @@ func TestMultiModelConcurrency(t *testing.T) { }, } resp = [2][]string{ - []string{"sunlight"}, - []string{"england", "english", "massachusetts", "pilgrims", "british"}, + {"sunlight"}, + {"england", "english", "massachusetts", "pilgrims", "british"}, } ) var wg sync.WaitGroup @@ -71,12 +71,11 @@ func TestIntegrationConcurrentPredictOrcaMini(t *testing.T) { reqLimit := len(req) iterLimit := 5 - vram := os.Getenv("OLLAMA_MAX_VRAM") // TODO - discover actual VRAM - if vram != "" { - max, err := strconv.ParseUint(vram, 10, 64) + if s := os.Getenv("OLLAMA_MAX_VRAM"); s != "" { + maxVram, err := strconv.ParseUint(s, 10, 64) require.NoError(t, err) // Don't hammer on small VRAM cards... - if max < 4*1024*1024*1024 { + if maxVram < 4*format.GibiByte { reqLimit = min(reqLimit, 2) iterLimit = 2 } @@ -233,12 +232,12 @@ func TestMultiModelStress(t *testing.T) { consumed := uint64(256 * format.MebiByte) // Assume some baseline usage for i := 0; i < len(req); i++ { // Always get at least 2 models, but dont' overshoot VRAM too much or we'll take too long - if i > 1 && consumed > vram { - slog.Info("achieved target vram exhaustion", "count", i, "vram", format.HumanBytes2(vram), "models", format.HumanBytes2(consumed)) + if i > 1 && consumed > maxVram { + slog.Info("achieved target vram exhaustion", "count", i, "vram", format.HumanBytes2(maxVram), "models", format.HumanBytes2(consumed)) break } consumed += chosenModels[i].size - slog.Info("target vram", "count", i, "vram", format.HumanBytes2(vram), "models", format.HumanBytes2(consumed)) + slog.Info("target vram", "count", i, "vram", format.HumanBytes2(maxVram), "models", format.HumanBytes2(consumed)) wg.Add(1) go func(i int) { diff --git a/integration/llm_test.go b/integration/llm_test.go index 4952b0726..398e0a03a 100644 --- a/integration/llm_test.go +++ b/integration/llm_test.go @@ -35,8 +35,8 @@ var ( }, } resp = [2][]string{ - []string{"sunlight"}, - []string{"england", "english", "massachusetts", "pilgrims"}, + {"sunlight"}, + {"england", "english", "massachusetts", "pilgrims"}, } ) diff --git a/integration/max_queue_test.go b/integration/max_queue_test.go index b06197e1f..ec9e085a5 100644 --- a/integration/max_queue_test.go +++ b/integration/max_queue_test.go @@ -29,7 +29,7 @@ func TestMaxQueue(t *testing.T) { // Also note that by default Darwin can't sustain > ~128 connections without adjusting limits threadCount := 32 if maxQueue := envconfig.MaxQueue(); maxQueue != 0 { - threadCount = maxQueue + threadCount = int(maxQueue) } else { t.Setenv("OLLAMA_MAX_QUEUE", strconv.Itoa(threadCount)) } diff --git a/integration/utils_test.go b/integration/utils_test.go index c2b27ee93..a60109958 100644 --- a/integration/utils_test.go +++ b/integration/utils_test.go @@ -334,10 +334,10 @@ func GenerateRequests() ([]api.GenerateRequest, [][]string) { }, }, [][]string{ - []string{"sunlight"}, - []string{"soil", "organic", "earth", "black", "tan"}, - []string{"england", "english", "massachusetts", "pilgrims", "british"}, - []string{"fourth", "july", "declaration", "independence"}, - []string{"nitrogen", "oxygen", "carbon", "dioxide"}, + {"sunlight"}, + {"soil", "organic", "earth", "black", "tan"}, + {"england", "english", "massachusetts", "pilgrims", "british"}, + {"fourth", "july", "declaration", "independence"}, + {"nitrogen", "oxygen", "carbon", "dioxide"}, } } From 86b907f82ad1cc5eb16e919d6cb5830765d73be4 Mon Sep 17 00:00:00 2001 From: royjhan <65097070+royjhan@users.noreply.github.com> Date: Mon, 5 Aug 2024 19:55:34 -0400 Subject: [PATCH 211/384] sort batch results (#6189) --- llm/ext_server/server.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/llm/ext_server/server.cpp b/llm/ext_server/server.cpp index d72bb1b14..071fe1e79 100644 --- a/llm/ext_server/server.cpp +++ b/llm/ext_server/server.cpp @@ -44,6 +44,7 @@ #include #endif +#include #include #include #include @@ -1220,6 +1221,7 @@ struct llama_server_context res.result_json = json { + {"id", res.id}, {"embedding", std::vector(embd, embd + n_embd)}, {"timings", slot.get_formated_timings()}, }; @@ -3203,6 +3205,10 @@ int main(int argc, char **argv) { } responses = result.result_json.value("results", std::vector{result.result_json}); + std::sort(responses.begin(), responses.end(), [](const json& a, const json& b) { + return a["id"] < b["id"]; + }); + json embeddings = json::array(); int prompt_n = 0; From fc85f50a2be9ba8776547de9db02c5373719eb13 Mon Sep 17 00:00:00 2001 From: Daniel Hiltgen Date: Tue, 6 Aug 2024 10:46:31 -0700 Subject: [PATCH 212/384] Ensure sparse files on windows during download The file.Truncate call on windows will write the whole file unless you set the sparse flag, leading to heavy I/O at the beginning of download. This should improve our I/O behavior on windows and put less stress on the users disk. --- server/download.go | 3 +++ server/sparse_common.go | 9 +++++++++ server/sparse_windows.go | 16 ++++++++++++++++ 3 files changed, 28 insertions(+) create mode 100644 server/sparse_common.go create mode 100644 server/sparse_windows.go diff --git a/server/download.go b/server/download.go index a903d96fc..38d24a6b2 100644 --- a/server/download.go +++ b/server/download.go @@ -216,6 +216,9 @@ func (b *blobDownload) run(ctx context.Context, requestURL *url.URL, opts *regis return err } defer file.Close() + if err := setSparse(file); err != nil { + return err + } _ = file.Truncate(b.Total) diff --git a/server/sparse_common.go b/server/sparse_common.go new file mode 100644 index 000000000..f25627fce --- /dev/null +++ b/server/sparse_common.go @@ -0,0 +1,9 @@ +//go:build !windows + +package server + +import "os" + +func setSparse(file *os.File) error { + return nil +} diff --git a/server/sparse_windows.go b/server/sparse_windows.go new file mode 100644 index 000000000..cdad379ea --- /dev/null +++ b/server/sparse_windows.go @@ -0,0 +1,16 @@ +package server + +import ( + "os" + + "golang.org/x/sys/windows" +) + +func setSparse(file *os.File) error { + return windows.DeviceIoControl( + windows.Handle(file.Fd()), windows.FSCTL_SET_SPARSE, + nil, 0, + nil, 0, + nil, nil, + ) +} From d4a7216c82bb406e644c739281ade3f7f2e283e5 Mon Sep 17 00:00:00 2001 From: Chua Chee Seng Date: Wed, 7 Aug 2024 02:37:16 +0800 Subject: [PATCH 213/384] Fixed invalid option provided not displaying the invalid option name problem. (#6202) --- api/types.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/types.go b/api/types.go index 291522a39..2f5a94241 100644 --- a/api/types.go +++ b/api/types.go @@ -504,7 +504,7 @@ func (opts *Options) FromMap(m map[string]interface{}) error { for key, val := range m { opt, ok := jsonOpts[key] if !ok { - slog.Warn("invalid option provided", "option", opt.Name) + slog.Warn("invalid option provided", "option", key) continue } From e04c7012c235d8972afe5538ff27802c77217b83 Mon Sep 17 00:00:00 2001 From: Jeffrey Morgan Date: Tue, 6 Aug 2024 15:11:45 -0400 Subject: [PATCH 214/384] update llama.cpp submodule to `1e6f6554` (#6208) --- llm/ext_server/server.cpp | 14 +++++++++++--- llm/llama.cpp | 2 +- llm/patches/09-lora.diff | 34 +++++++++++++--------------------- llm/patches/10-params.diff | 20 -------------------- 4 files changed, 25 insertions(+), 45 deletions(-) delete mode 100644 llm/patches/10-params.diff diff --git a/llm/ext_server/server.cpp b/llm/ext_server/server.cpp index 071fe1e79..c65901c7c 100644 --- a/llm/ext_server/server.cpp +++ b/llm/ext_server/server.cpp @@ -403,7 +403,9 @@ struct llama_server_context } } - std::tie(model, ctx) = llama_init_from_gpt_params(params); + auto init_result = llama_init_from_gpt_params(params); + model = init_result.model; + ctx = init_result.context; if (model == nullptr) { LOG_ERROR("unable to load model", {{"model", params.model}}); @@ -2422,7 +2424,10 @@ static void server_params_parse(int argc, char **argv, server_params &sparams, g invalid_param = true; break; } - params.lora_adapter.emplace_back(argv[i], 1.0f); + params.lora_adapters.push_back({ + std::string(argv[i]), + 1.0, + }); params.use_mmap = false; } else if (arg == "--lora-scaled") @@ -2438,7 +2443,10 @@ static void server_params_parse(int argc, char **argv, server_params &sparams, g invalid_param = true; break; } - params.lora_adapter.emplace_back(lora_adapter, std::stof(argv[i])); + params.lora_adapters.push_back({ + lora_adapter, + std::stof(argv[i]) + }); params.use_mmap = false; } else if (arg == "-v" || arg == "--verbose") diff --git a/llm/llama.cpp b/llm/llama.cpp index 6eeaeba12..1e6f6554a 160000 --- a/llm/llama.cpp +++ b/llm/llama.cpp @@ -1 +1 @@ -Subproject commit 6eeaeba126ff701f3e8f79f246805b7023709972 +Subproject commit 1e6f6554aa11fa10160a5fda689e736c3c34169f diff --git a/llm/patches/09-lora.diff b/llm/patches/09-lora.diff index 10c66d1d5..219584767 100644 --- a/llm/patches/09-lora.diff +++ b/llm/patches/09-lora.diff @@ -1,40 +1,32 @@ diff --git a/common/common.cpp b/common/common.cpp -index dbb724fb..c26fe6ee 100644 +index 2e8374d5..70d0afde 100644 --- a/common/common.cpp +++ b/common/common.cpp -@@ -2087,14 +2087,27 @@ std::tuple llama_init_from_gpt_par - for (unsigned int i = 0; i < params.lora_adapter.size(); ++i) { - const std::string & lora_adapter = std::get<0>(params.lora_adapter[i]); - float lora_scale = std::get<1>(params.lora_adapter[i]); -+ -+ // try to load as gguf - auto adapter = llama_lora_adapter_init(model, lora_adapter.c_str()); - if (adapter == nullptr) { -- fprintf(stderr, "%s: error: failed to apply lora adapter\n", __func__); +@@ -2110,9 +2110,21 @@ struct llama_init_result llama_init_from_gpt_params(gpt_params & params) { + loaded_la.adapter = llama_lora_adapter_init(model, la.path.c_str()); + if (loaded_la.adapter == nullptr) { + fprintf(stderr, "%s: error: failed to apply lora adapter '%s'\n", __func__, la.path.c_str()); - llama_free(lctx); - llama_free_model(model); -- return std::make_tuple(nullptr, nullptr); -+ fprintf(stderr, "%s: error: failed to apply lora adapter, trying ggla\n", __func__); +- return iparams; + + // if that fails, try loading as ggla for compatibility + int err = llama_model_apply_lora_from_file(model, -+ lora_adapter.c_str(), -+ lora_scale, ++ la.path.c_str(), ++ la.scale, + nullptr, + params.n_threads); + if (err != 0) { + fprintf(stderr, "%s: error: failed to apply lora adapter\n", __func__); + llama_free(lctx); + llama_free_model(model); -+ return std::make_tuple(nullptr, nullptr); ++ return iparams; ++ } else { ++ break; + } -+ } else { -+ llama_lora_adapter_set(lctx, adapter, lora_scale); } -- llama_lora_adapter_set(lctx, adapter, lora_scale); + iparams.lora_adapters.push_back(loaded_la); // copy to list of loaded adapters } - - if (params.ignore_eos) { diff --git a/include/llama.h b/include/llama.h index 93fd77ca..b0fb37a6 100644 --- a/include/llama.h @@ -355,4 +347,4 @@ index 80a0dd0f..9d7b0e17 100644 + return 1; + } +} -\ No newline at end of file +\ No newline at end of file \ No newline at end of file diff --git a/llm/patches/10-params.diff b/llm/patches/10-params.diff deleted file mode 100644 index 56699b8ec..000000000 --- a/llm/patches/10-params.diff +++ /dev/null @@ -1,20 +0,0 @@ -diff --git a/src/llama.cpp b/src/llama.cpp -index a207451f..fba6b175 100644 ---- a/src/llama.cpp -+++ b/src/llama.cpp -@@ -4969,6 +4969,7 @@ static void llm_load_hparams( - hparams.attn_soft_cap = true; - - switch (hparams.n_layer) { -+ case 26: model.type = e_model::MODEL_2B; break; - case 42: model.type = e_model::MODEL_9B; break; - case 46: model.type = e_model::MODEL_27B; break; - default: model.type = e_model::MODEL_UNKNOWN; -@@ -11736,6 +11737,7 @@ struct llm_build_context { - - // ref: https://github.com/google/gemma_pytorch/commit/03e657582d17cb5a8617ebf333c1c16f3694670e - switch (model.type) { -+ case e_model::MODEL_2B: Qcur = ggml_scale(ctx0, Qcur, 1.0f / sqrtf(float(n_embd_head_k))); break; - case e_model::MODEL_9B: Qcur = ggml_scale(ctx0, Qcur, 1.0f / sqrtf(float(n_embd_head_k))); break; - case e_model::MODEL_27B: Qcur = ggml_scale(ctx0, Qcur, 1.0f / sqrtf(float(n_embd / n_head))); break; - default: GGML_ABORT("fatal error"); From de4fc297732cb60ff79a6c8010a7c79971c21b4a Mon Sep 17 00:00:00 2001 From: Jeffrey Morgan Date: Tue, 6 Aug 2024 23:20:49 -0400 Subject: [PATCH 215/384] llm: reserve required number of slots for embeddings (#6219) --- llm/server.go | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/llm/server.go b/llm/server.go index 152b7582f..41736068c 100644 --- a/llm/server.go +++ b/llm/server.go @@ -44,11 +44,12 @@ type LlamaServer interface { // llmServer is an instance of the llama.cpp server type llmServer struct { - port int - cmd *exec.Cmd - done chan error // Channel to signal when the process exits - status *StatusWriter - options api.Options + port int + cmd *exec.Cmd + done chan error // Channel to signal when the process exits + status *StatusWriter + options api.Options + numParallel int estimate MemoryEstimate totalLayers uint64 @@ -343,6 +344,7 @@ func NewLlamaServer(gpus gpu.GpuInfoList, model string, ggml *GGML, adapters, pr status: NewStatusWriter(os.Stderr), options: opts, estimate: estimate, + numParallel: numParallel, sem: semaphore.NewWeighted(int64(numParallel)), totalLayers: ggml.KV().BlockCount() + 1, gpus: gpus, @@ -890,11 +892,14 @@ type EmbedResponse struct { } func (s *llmServer) Embed(ctx context.Context, input []string) (*EmbedResponse, error) { - if err := s.sem.Acquire(ctx, 1); err != nil { + // each input will use a slot, so we need to acquire the semaphore for + // the number of inputs up to numParallel + slots := int64(min(len(input), s.numParallel)) + if err := s.sem.Acquire(ctx, slots); err != nil { slog.Error("Failed to acquire semaphore", "error", err) return nil, err } - defer s.sem.Release(1) + defer s.sem.Release(slots) // Make sure the server is ready status, err := s.getServerStatusRetry(ctx) From 685a53534b80a14efdfdb09ca00af984782ba6ee Mon Sep 17 00:00:00 2001 From: Jesse Gross Date: Thu, 1 Aug 2024 15:05:16 -0700 Subject: [PATCH 216/384] manifest: Don't prune layers if we can't open a manifest file If there is an error when opening a manifest file (corrupted, permission denied, etc.) then the referenced layers will not be included in the list of active layers. This causes them to be deleted when pruning happens at startup or a model is pulled. In such a situation, we should prefer to preserve data in the hopes that it can be recovered rather than being agressive about deletion. --- server/images.go | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/server/images.go b/server/images.go index 81357f3c0..05875a88f 100644 --- a/server/images.go +++ b/server/images.go @@ -714,8 +714,7 @@ func deleteUnusedLayers(skipModelPath *ModelPath, deleteMap map[string]struct{}) // save (i.e. delete from the deleteMap) any files used in other manifests manifest, _, err := GetManifest(fmp) if err != nil { - //nolint:nilerr - return nil + return err } for _, layer := range manifest.Layers { @@ -782,7 +781,8 @@ func PruneLayers() error { err = deleteUnusedLayers(nil, deleteMap) if err != nil { - return err + slog.Info(fmt.Sprintf("couldn't remove unused layers: %v", err)) + return nil } slog.Info(fmt.Sprintf("total unused blobs removed: %d", len(deleteMap))) @@ -971,7 +971,8 @@ func PullModel(ctx context.Context, name string, regOpts *registryOptions, fn fu fn(api.ProgressResponse{Status: "removing any unused layers"}) err = deleteUnusedLayers(nil, deleteMap) if err != nil { - return err + slog.Info(fmt.Sprintf("couldn't remove unused layers: %v", err)) + fn(api.ProgressResponse{Status: fmt.Sprintf("couldn't remove unused layers: %v", err)}) } } From ce67706037a2583157fcac4cbf6253fe0f1e5139 Mon Sep 17 00:00:00 2001 From: Nicholas Schwab Date: Wed, 7 Aug 2024 18:15:17 +0200 Subject: [PATCH 217/384] Set *.png and *.ico to be treated as binary files. The change b732beba6 makes all files text files and sets lf as eol. This will automatically change all files to have lf if they are touched by git (e.g. via git status). This change cannot be stashed and makes it hard to work with the repo (rebase and checkout don't really work). See also #6183. Here, we set the offending files (*.png and *.ico, but that might be more in the future) to be treated as binary files and not be changed by git. --- .gitattributes | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitattributes b/.gitattributes index f7192096c..648c78ca4 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,2 +1,4 @@ llm/ext_server/* linguist-vendored * text eol=lf +*.png binary +*.ico binary From 1829fb61bd7a4186881714618f09b2877d0bc9a3 Mon Sep 17 00:00:00 2001 From: Jesse Gross Date: Mon, 5 Aug 2024 17:13:52 -0700 Subject: [PATCH 218/384] manifest: Fix crash on startup when trying to clean up unused files (#5840) Currently if the config field is missing in the manifest file (or corrupted), Ollama will crash when it tries to read it. This can happen at startup or when pulling new models. This data is mostly just used for showing model information so we can be tolerant of it not being present - it is not required to run the models. Besides avoiding crashing, this also gives us the ability to restructure the config in the future by pulling it into the main manifest file. --- server/images.go | 40 ++++++++++++++++++++++++---------------- server/layer.go | 15 ++++++++++++++- server/manifest.go | 18 ++++++++++-------- server/routes.go | 23 +++++++++++++---------- 4 files changed, 61 insertions(+), 35 deletions(-) diff --git a/server/images.go b/server/images.go index 05875a88f..7ed359955 100644 --- a/server/images.go +++ b/server/images.go @@ -250,19 +250,21 @@ func GetModel(name string) (*Model, error) { Template: template.DefaultTemplate, } - filename, err := GetBlobsPath(manifest.Config.Digest) - if err != nil { - return nil, err - } + if manifest.Config.Digest != "" { + 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() + 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 + if err := json.NewDecoder(configFile).Decode(&model.Config); err != nil { + return nil, err + } } for _, layer := range manifest.Layers { @@ -781,7 +783,7 @@ func PruneLayers() error { err = deleteUnusedLayers(nil, deleteMap) if err != nil { - slog.Info(fmt.Sprintf("couldn't remove unused layers: %v", err)) + slog.Error(fmt.Sprintf("couldn't remove unused layers: %v", err)) return nil } @@ -839,7 +841,9 @@ func PushModel(ctx context.Context, name string, regOpts *registryOptions, fn fu var layers []*Layer layers = append(layers, manifest.Layers...) - layers = append(layers, manifest.Config) + if manifest.Config.Digest != "" { + layers = append(layers, &manifest.Config) + } for _, layer := range layers { if err := uploadBlob(ctx, mp, layer, regOpts, fn); err != nil { @@ -890,7 +894,9 @@ func PullModel(ctx context.Context, name string, regOpts *registryOptions, fn fu for _, l := range manifest.Layers { deleteMap[l.Digest] = struct{}{} } - deleteMap[manifest.Config.Digest] = struct{}{} + if manifest.Config.Digest != "" { + deleteMap[manifest.Config.Digest] = struct{}{} + } } } @@ -907,7 +913,9 @@ func PullModel(ctx context.Context, name string, regOpts *registryOptions, fn fu var layers []*Layer layers = append(layers, manifest.Layers...) - layers = append(layers, manifest.Config) + if manifest.Config.Digest != "" { + layers = append(layers, &manifest.Config) + } skipVerify := make(map[string]bool) for _, layer := range layers { @@ -971,7 +979,7 @@ func PullModel(ctx context.Context, name string, regOpts *registryOptions, fn fu fn(api.ProgressResponse{Status: "removing any unused layers"}) err = deleteUnusedLayers(nil, deleteMap) if err != nil { - slog.Info(fmt.Sprintf("couldn't remove unused layers: %v", err)) + slog.Error(fmt.Sprintf("couldn't remove unused layers: %v", err)) fn(api.ProgressResponse{Status: fmt.Sprintf("couldn't remove unused layers: %v", err)}) } } diff --git a/server/layer.go b/server/layer.go index cc6709d24..a2b667824 100644 --- a/server/layer.go +++ b/server/layer.go @@ -2,6 +2,7 @@ package server import ( "crypto/sha256" + "errors" "fmt" "io" "os" @@ -61,6 +62,10 @@ func NewLayer(r io.Reader, mediatype string) (*Layer, error) { } func NewLayerFromLayer(digest, mediatype, from string) (*Layer, error) { + if digest == "" { + return nil, errors.New("creating new layer from layer with empty digest") + } + blob, err := GetBlobsPath(digest) if err != nil { return nil, err @@ -81,6 +86,10 @@ func NewLayerFromLayer(digest, mediatype, from string) (*Layer, error) { } func (l *Layer) Open() (io.ReadSeekCloser, error) { + if l.Digest == "" { + return nil, errors.New("opening layer with empty digest") + } + blob, err := GetBlobsPath(l.Digest) if err != nil { return nil, err @@ -90,13 +99,17 @@ func (l *Layer) Open() (io.ReadSeekCloser, error) { } func (l *Layer) Remove() error { + if l.Digest == "" { + return nil + } + ms, err := Manifests() if err != nil { return err } for _, m := range ms { - for _, layer := range append(m.Layers, m.Config) { + for _, layer := range append(m.Layers, &m.Config) { if layer.Digest == l.Digest { // something is using this layer return nil diff --git a/server/manifest.go b/server/manifest.go index b8df11efb..b966ddbe3 100644 --- a/server/manifest.go +++ b/server/manifest.go @@ -16,7 +16,7 @@ import ( type Manifest struct { SchemaVersion int `json:"schemaVersion"` MediaType string `json:"mediaType"` - Config *Layer `json:"config"` + Config Layer `json:"config"` Layers []*Layer `json:"layers"` filepath string @@ -25,7 +25,7 @@ type Manifest struct { } func (m *Manifest) Size() (size int64) { - for _, layer := range append(m.Layers, m.Config) { + for _, layer := range append(m.Layers, &m.Config) { size += layer.Size } @@ -46,11 +46,13 @@ func (m *Manifest) Remove() error { } func (m *Manifest) RemoveLayers() error { - for _, layer := range append(m.Layers, m.Config) { - if err := layer.Remove(); errors.Is(err, os.ErrNotExist) { - slog.Debug("layer does not exist", "digest", layer.Digest) - } else if err != nil { - return err + for _, layer := range append(m.Layers, &m.Config) { + if layer.Digest != "" { + if err := layer.Remove(); errors.Is(err, os.ErrNotExist) { + slog.Debug("layer does not exist", "digest", layer.Digest) + } else if err != nil { + return err + } } } @@ -113,7 +115,7 @@ func WriteManifest(name model.Name, config *Layer, layers []*Layer) error { m := Manifest{ SchemaVersion: 2, MediaType: "application/vnd.docker.distribution.manifest.v2+json", - Config: config, + Config: *config, Layers: layers, } diff --git a/server/routes.go b/server/routes.go index b9c66b650..e55eaa9d4 100644 --- a/server/routes.go +++ b/server/routes.go @@ -824,17 +824,20 @@ func (s *Server) ListModelsHandler(c *gin.Context) { models := []api.ListModelResponse{} for n, m := range ms { - f, err := m.Config.Open() - if err != nil { - slog.Warn("bad manifest filepath", "name", n, "error", err) - continue - } - defer f.Close() - var cf ConfigV2 - if err := json.NewDecoder(f).Decode(&cf); err != nil { - slog.Warn("bad manifest config", "name", n, "error", err) - continue + + if m.Config.Digest != "" { + f, err := m.Config.Open() + if err != nil { + slog.Warn("bad manifest filepath", "name", n, "error", err) + continue + } + defer f.Close() + + if err := json.NewDecoder(f).Decode(&cf); err != nil { + slog.Warn("bad manifest config", "name", n, "error", err) + continue + } } // tag should never be masked From ad0c19dde403ba67aa27247775e33c33c30ee235 Mon Sep 17 00:00:00 2001 From: Kyle Kelley Date: Wed, 7 Aug 2024 14:20:50 -0700 Subject: [PATCH 219/384] Use llama3.1 in tools example (#5985) * Use llama3.1 in tools example * Update api.md --- docs/api.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/api.md b/docs/api.md index c0202ef3e..5cbba5237 100644 --- a/docs/api.md +++ b/docs/api.md @@ -669,7 +669,7 @@ curl http://localhost:11434/api/chat -d '{ ``` curl http://localhost:11434/api/chat -d '{ - "model": "mistral", + "model": "llama3.1", "messages": [ { "role": "user", @@ -708,7 +708,7 @@ curl http://localhost:11434/api/chat -d '{ ```json { - "model": "mistral:7b-instruct-v0.3-q4_K_M", + "model": "llama3.1", "created_at": "2024-07-22T20:33:28.123648Z", "message": { "role": "assistant", From 5b3a21b578da89b1682a98ce123a6b3c91697e9b Mon Sep 17 00:00:00 2001 From: royjhan <65097070+royjhan@users.noreply.github.com> Date: Wed, 7 Aug 2024 17:43:44 -0400 Subject: [PATCH 220/384] add metrics to docs (#6079) --- docs/api.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/api.md b/docs/api.md index 5cbba5237..aed2b69fb 100644 --- a/docs/api.md +++ b/docs/api.md @@ -1175,7 +1175,10 @@ curl http://localhost:11434/api/embed -d '{ "embeddings": [[ 0.010071029, -0.0017594862, 0.05007221, 0.04692972, 0.054916814, 0.008599704, 0.105441414, -0.025878139, 0.12958129, 0.031952348 - ]] + ]], + "total_duration": 14143917, + "load_duration": 1019500, + "prompt_eval_count": 8 } ``` From 97ec8cfd4ef13190f3939fbb24b6f146d570ed12 Mon Sep 17 00:00:00 2001 From: Jesse Gross Date: Wed, 7 Aug 2024 11:44:25 -0700 Subject: [PATCH 221/384] image: Clarify argument to WriteManifest is config When creating a model the config layer is appended to the list of layers and then the last layer is used as the config when writing the manifest. This change directly uses the config layer to write the manifest. There is no behavior change but it is less error prone. --- server/images.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/server/images.go b/server/images.go index 7ed359955..4202a4132 100644 --- a/server/images.go +++ b/server/images.go @@ -625,12 +625,12 @@ func CreateModel(ctx context.Context, name model.Name, modelFileDir, quantizatio return err } - layer, err := NewLayer(&b, "application/vnd.docker.container.image.v1+json") + configLayer, err := NewLayer(&b, "application/vnd.docker.container.image.v1+json") if err != nil { return err } - for _, layer := range append(layers, layer) { + for _, layer := range append(layers, configLayer) { if layer.status != "" { fn(api.ProgressResponse{Status: layer.status}) } @@ -639,7 +639,7 @@ func CreateModel(ctx context.Context, name model.Name, modelFileDir, quantizatio old, _ := ParseNamedManifest(name) fn(api.ProgressResponse{Status: "writing manifest"}) - if err := WriteManifest(name, layer, layers); err != nil { + if err := WriteManifest(name, configLayer, layers); err != nil { return err } From 7edaf6e7e8d79a9c88419988ae98afaf3fc32f15 Mon Sep 17 00:00:00 2001 From: Jesse Gross Date: Wed, 7 Aug 2024 14:22:17 -0700 Subject: [PATCH 222/384] manifest: Store layers inside manifests consistently as values. Commit 1829fb61 ("manifest: Fix crash on startup when trying to clean up unused files (#5840)") changed the config layer stored in manifests from a pointer to a value. This was done in order to avoid potential nil pointer dereferences after it is deserialized from JSON in the event that the field is missing. This changes the Layers slice to also be stored by value. This enables consistency in handling across the two objects. --- server/images.go | 14 +++++++------- server/layer.go | 28 ++++++++++++++-------------- server/manifest.go | 16 ++++++++-------- server/model.go | 2 +- server/routes_delete_test.go | 2 +- server/upload.go | 4 ++-- 6 files changed, 33 insertions(+), 33 deletions(-) diff --git a/server/images.go b/server/images.go index 4202a4132..0e753f566 100644 --- a/server/images.go +++ b/server/images.go @@ -373,7 +373,7 @@ func CreateModel(ctx context.Context, name model.Name, modelFileDir, quantizatio var messages []*api.Message parameters := make(map[string]any) - var layers []*Layer + var layers []Layer for _, c := range modelfile.Commands { mediatype := fmt.Sprintf("application/vnd.ollama.image.%s", c.Name) @@ -499,7 +499,7 @@ func CreateModel(ctx context.Context, name model.Name, modelFileDir, quantizatio if c.Name != "license" { // replace - layers = slices.DeleteFunc(layers, func(layer *Layer) bool { + layers = slices.DeleteFunc(layers, func(layer Layer) bool { if layer.MediaType != mediatype { return false } @@ -545,7 +545,7 @@ func CreateModel(ctx context.Context, name model.Name, modelFileDir, quantizatio } var err2 error - layers = slices.DeleteFunc(layers, func(layer *Layer) bool { + layers = slices.DeleteFunc(layers, func(layer Layer) bool { switch layer.MediaType { case "application/vnd.ollama.image.message": // if there are new messages, remove the inherited ones @@ -839,10 +839,10 @@ func PushModel(ctx context.Context, name string, regOpts *registryOptions, fn fu return err } - var layers []*Layer + var layers []Layer layers = append(layers, manifest.Layers...) if manifest.Config.Digest != "" { - layers = append(layers, &manifest.Config) + layers = append(layers, manifest.Config) } for _, layer := range layers { @@ -911,10 +911,10 @@ func PullModel(ctx context.Context, name string, regOpts *registryOptions, fn fu return fmt.Errorf("pull model manifest: %s", err) } - var layers []*Layer + var layers []Layer layers = append(layers, manifest.Layers...) if manifest.Config.Digest != "" { - layers = append(layers, &manifest.Config) + layers = append(layers, manifest.Config) } skipVerify := make(map[string]bool) diff --git a/server/layer.go b/server/layer.go index a2b667824..c666bd106 100644 --- a/server/layer.go +++ b/server/layer.go @@ -16,15 +16,15 @@ type Layer struct { status string } -func NewLayer(r io.Reader, mediatype string) (*Layer, error) { +func NewLayer(r io.Reader, mediatype string) (Layer, error) { blobs, err := GetBlobsPath("") if err != nil { - return nil, err + return Layer{}, err } temp, err := os.CreateTemp(blobs, "sha256-") if err != nil { - return nil, err + return Layer{}, err } defer temp.Close() defer os.Remove(temp.Name()) @@ -32,28 +32,28 @@ func NewLayer(r io.Reader, mediatype string) (*Layer, error) { sha256sum := sha256.New() n, err := io.Copy(io.MultiWriter(temp, sha256sum), r) if err != nil { - return nil, err + return Layer{}, err } if err := temp.Close(); err != nil { - return nil, err + return Layer{}, err } digest := fmt.Sprintf("sha256:%x", sha256sum.Sum(nil)) blob, err := GetBlobsPath(digest) if err != nil { - return nil, err + return Layer{}, err } status := "using existing layer" if _, err := os.Stat(blob); err != nil { status = "creating new layer" if err := os.Rename(temp.Name(), blob); err != nil { - return nil, err + return Layer{}, err } } - return &Layer{ + return Layer{ MediaType: mediatype, Digest: digest, Size: n, @@ -61,22 +61,22 @@ func NewLayer(r io.Reader, mediatype string) (*Layer, error) { }, nil } -func NewLayerFromLayer(digest, mediatype, from string) (*Layer, error) { +func NewLayerFromLayer(digest, mediatype, from string) (Layer, error) { if digest == "" { - return nil, errors.New("creating new layer from layer with empty digest") + return Layer{}, errors.New("creating new layer from layer with empty digest") } blob, err := GetBlobsPath(digest) if err != nil { - return nil, err + return Layer{}, err } fi, err := os.Stat(blob) if err != nil { - return nil, err + return Layer{}, err } - return &Layer{ + return Layer{ MediaType: mediatype, Digest: digest, Size: fi.Size(), @@ -109,7 +109,7 @@ func (l *Layer) Remove() error { } for _, m := range ms { - for _, layer := range append(m.Layers, &m.Config) { + for _, layer := range append(m.Layers, m.Config) { if layer.Digest == l.Digest { // something is using this layer return nil diff --git a/server/manifest.go b/server/manifest.go index b966ddbe3..6a5d7b885 100644 --- a/server/manifest.go +++ b/server/manifest.go @@ -14,10 +14,10 @@ import ( ) type Manifest struct { - SchemaVersion int `json:"schemaVersion"` - MediaType string `json:"mediaType"` - Config Layer `json:"config"` - Layers []*Layer `json:"layers"` + SchemaVersion int `json:"schemaVersion"` + MediaType string `json:"mediaType"` + Config Layer `json:"config"` + Layers []Layer `json:"layers"` filepath string fi os.FileInfo @@ -25,7 +25,7 @@ type Manifest struct { } func (m *Manifest) Size() (size int64) { - for _, layer := range append(m.Layers, &m.Config) { + for _, layer := range append(m.Layers, m.Config) { size += layer.Size } @@ -46,7 +46,7 @@ func (m *Manifest) Remove() error { } func (m *Manifest) RemoveLayers() error { - for _, layer := range append(m.Layers, &m.Config) { + for _, layer := range append(m.Layers, m.Config) { if layer.Digest != "" { if err := layer.Remove(); errors.Is(err, os.ErrNotExist) { slog.Debug("layer does not exist", "digest", layer.Digest) @@ -95,7 +95,7 @@ func ParseNamedManifest(n model.Name) (*Manifest, error) { return &m, nil } -func WriteManifest(name model.Name, config *Layer, layers []*Layer) error { +func WriteManifest(name model.Name, config Layer, layers []Layer) error { manifests, err := GetManifestPath() if err != nil { return err @@ -115,7 +115,7 @@ func WriteManifest(name model.Name, config *Layer, layers []*Layer) error { m := Manifest{ SchemaVersion: 2, MediaType: "application/vnd.docker.distribution.manifest.v2+json", - Config: *config, + Config: config, Layers: layers, } diff --git a/server/model.go b/server/model.go index f2946a0be..ad6e4e552 100644 --- a/server/model.go +++ b/server/model.go @@ -26,7 +26,7 @@ import ( var intermediateBlobs map[string]string = make(map[string]string) type layerGGML struct { - *Layer + Layer *llm.GGML } diff --git a/server/routes_delete_test.go b/server/routes_delete_test.go index 1c950d669..82fac9f52 100644 --- a/server/routes_delete_test.go +++ b/server/routes_delete_test.go @@ -98,7 +98,7 @@ func TestDeleteDuplicateLayers(t *testing.T) { } // create a manifest with duplicate layers - if err := WriteManifest(n, config, []*Layer{config}); err != nil { + if err := WriteManifest(n, config, []Layer{config}); err != nil { t.Fatal(err) } diff --git a/server/upload.go b/server/upload.go index b5a244ea8..2f115436e 100644 --- a/server/upload.go +++ b/server/upload.go @@ -26,7 +26,7 @@ import ( var blobUploadManager sync.Map type blobUpload struct { - *Layer + Layer Total int64 Completed atomic.Int64 @@ -362,7 +362,7 @@ func (p *progressWriter) Rollback() { p.written = 0 } -func uploadBlob(ctx context.Context, mp ModelPath, layer *Layer, opts *registryOptions, fn func(api.ProgressResponse)) error { +func uploadBlob(ctx context.Context, mp ModelPath, layer Layer, opts *registryOptions, fn func(api.ProgressResponse)) error { requestURL := mp.BaseURL() requestURL = requestURL.JoinPath("v2", mp.GetNamespaceRepository(), "blobs", layer.Digest) From 7b61eba47159748bcfc35227a13e31c899a84e49 Mon Sep 17 00:00:00 2001 From: Jitang Lei Date: Thu, 8 Aug 2024 20:28:01 +0800 Subject: [PATCH 223/384] server/download.go: Fix a typo in log Signed-off-by: Jitang Lei --- server/download.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/download.go b/server/download.go index 38d24a6b2..cf31df5e8 100644 --- a/server/download.go +++ b/server/download.go @@ -235,7 +235,7 @@ func (b *blobDownload) run(ctx context.Context, requestURL *url.URL, opts *regis newOpts.CheckRedirect = func(req *http.Request, via []*http.Request) error { if len(via) > 10 { - return errors.New("maxium redirects exceeded (10) for directURL") + return errors.New("maximum redirects exceeded (10) for directURL") } // if the hostname is the same, allow the redirect From 2003d601594c89931d4741d20ffa730a14bbb4d9 Mon Sep 17 00:00:00 2001 From: Michael Yang Date: Thu, 8 Aug 2024 11:18:13 -0700 Subject: [PATCH 224/384] llama3.1 memory --- llm/ggml.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/llm/ggml.go b/llm/ggml.go index d7f2eef7c..bde9c3473 100644 --- a/llm/ggml.go +++ b/llm/ggml.go @@ -344,11 +344,13 @@ func (llm GGML) GraphSize(context, batch uint64) (partialOffload, fullOffload ui switch llm.KV().Architecture() { case "llama": - fullOffload = 4 * batch * (1 + 4*embedding + context*(1+heads)) + fullOffload = max( + 4*batch*(1+4*embedding+context*(1+heads)), + 4*batch*(embedding+vocab), + ) partialOffload = 4 * batch * embedding partialOffload += max( - // 4*batch*(4+6*embedding+context*(2*heads)+llm.KV().GQA()), 4*batch*(1+embedding+max(context, embedding))+embedding*embedding*9/16+4*context*(batch*heads+embeddingHeads*headsKV), 4*batch*(embedding+vocab)+embedding*vocab*105/128, ) From 67472e0e89f516ccfbfad2d11414aadf484b7642 Mon Sep 17 00:00:00 2001 From: Nicholas42 Date: Fri, 9 Aug 2024 13:41:20 +0200 Subject: [PATCH 225/384] Also flag *.icns as binary --- .gitattributes | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitattributes b/.gitattributes index 648c78ca4..baabd3c13 100644 --- a/.gitattributes +++ b/.gitattributes @@ -2,3 +2,4 @@ llm/ext_server/* linguist-vendored * text eol=lf *.png binary *.ico binary +*.icns binary From 5bca2e60a7baefe582077469a1d14ff516b5d322 Mon Sep 17 00:00:00 2001 From: Daniel Hiltgen Date: Fri, 9 Aug 2024 11:31:38 -0700 Subject: [PATCH 226/384] Harden intel boostrap for nil pointers --- gpu/gpu.go | 61 ++++++++++++++++++++++++++++-------------------------- 1 file changed, 32 insertions(+), 29 deletions(-) diff --git a/gpu/gpu.go b/gpu/gpu.go index 7ae8fbec1..dc124a3ed 100644 --- a/gpu/gpu.go +++ b/gpu/gpu.go @@ -305,38 +305,41 @@ func GetGPUInfo() GpuInfoList { // Intel if envconfig.IntelGPU() { oHandles = initOneAPIHandles() - // On windows we bundle the oneapi library one level above the runner dir - depPath = "" - if runtime.GOOS == "windows" && envconfig.RunnersDir() != "" { - depPath = filepath.Join(filepath.Dir(envconfig.RunnersDir()), "oneapi") - } + if oHandles != nil && oHandles.oneapi != nil { - for d := range oHandles.oneapi.num_drivers { - if oHandles.oneapi == nil { - // shouldn't happen - slog.Warn("nil oneapi handle with driver count", "count", int(oHandles.oneapi.num_drivers)) - continue + // On windows we bundle the oneapi library one level above the runner dir + depPath = "" + if runtime.GOOS == "windows" && envconfig.RunnersDir() != "" { + depPath = filepath.Join(filepath.Dir(envconfig.RunnersDir()), "oneapi") } - devCount := C.oneapi_get_device_count(*oHandles.oneapi, C.int(d)) - for i := range devCount { - gpuInfo := OneapiGPUInfo{ - GpuInfo: GpuInfo{ - Library: "oneapi", - }, - driverIndex: int(d), - gpuIndex: int(i), + + for d := range oHandles.oneapi.num_drivers { + if oHandles.oneapi == nil { + // shouldn't happen + slog.Warn("nil oneapi handle with driver count", "count", int(oHandles.oneapi.num_drivers)) + continue + } + devCount := C.oneapi_get_device_count(*oHandles.oneapi, C.int(d)) + for i := range devCount { + gpuInfo := OneapiGPUInfo{ + GpuInfo: GpuInfo{ + Library: "oneapi", + }, + driverIndex: int(d), + gpuIndex: int(i), + } + // TODO - split bootstrapping from updating free memory + C.oneapi_check_vram(*oHandles.oneapi, C.int(d), i, &memInfo) + // TODO - convert this to MinimumMemory based on testing... + var totalFreeMem float64 = float64(memInfo.free) * 0.95 // work-around: leave some reserve vram for mkl lib used in ggml-sycl backend. + memInfo.free = C.uint64_t(totalFreeMem) + gpuInfo.TotalMemory = uint64(memInfo.total) + gpuInfo.FreeMemory = uint64(memInfo.free) + gpuInfo.ID = C.GoString(&memInfo.gpu_id[0]) + gpuInfo.Name = C.GoString(&memInfo.gpu_name[0]) + gpuInfo.DependencyPath = depPath + oneapiGPUs = append(oneapiGPUs, gpuInfo) } - // TODO - split bootstrapping from updating free memory - C.oneapi_check_vram(*oHandles.oneapi, C.int(d), i, &memInfo) - // TODO - convert this to MinimumMemory based on testing... - var totalFreeMem float64 = float64(memInfo.free) * 0.95 // work-around: leave some reserve vram for mkl lib used in ggml-sycl backend. - memInfo.free = C.uint64_t(totalFreeMem) - gpuInfo.TotalMemory = uint64(memInfo.total) - gpuInfo.FreeMemory = uint64(memInfo.free) - gpuInfo.ID = C.GoString(&memInfo.gpu_id[0]) - gpuInfo.Name = C.GoString(&memInfo.gpu_name[0]) - gpuInfo.DependencyPath = depPath - oneapiGPUs = append(oneapiGPUs, gpuInfo) } } } From 2fa1db434581bcfcb6fec1482904656e4b5f8313 Mon Sep 17 00:00:00 2001 From: Daniel Hiltgen Date: Fri, 9 Aug 2024 11:57:48 -0700 Subject: [PATCH 227/384] Don't hard fail on sparse setup error It seems this can fail in some casees, but proceed with the download anyway. --- server/download.go | 4 +--- server/sparse_common.go | 3 +-- server/sparse_windows.go | 5 +++-- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/server/download.go b/server/download.go index 38d24a6b2..5965b322e 100644 --- a/server/download.go +++ b/server/download.go @@ -216,9 +216,7 @@ func (b *blobDownload) run(ctx context.Context, requestURL *url.URL, opts *regis return err } defer file.Close() - if err := setSparse(file); err != nil { - return err - } + setSparse(file) _ = file.Truncate(b.Total) diff --git a/server/sparse_common.go b/server/sparse_common.go index f25627fce..c88b2da0b 100644 --- a/server/sparse_common.go +++ b/server/sparse_common.go @@ -4,6 +4,5 @@ package server import "os" -func setSparse(file *os.File) error { - return nil +func setSparse(*os.File) { } diff --git a/server/sparse_windows.go b/server/sparse_windows.go index cdad379ea..f21cbbda7 100644 --- a/server/sparse_windows.go +++ b/server/sparse_windows.go @@ -6,8 +6,9 @@ import ( "golang.org/x/sys/windows" ) -func setSparse(file *os.File) error { - return windows.DeviceIoControl( +func setSparse(file *os.File) { + // exFat (and other FS types) don't support sparse files, so ignore errors + windows.DeviceIoControl( //nolint:errcheck windows.Handle(file.Fd()), windows.FSCTL_SET_SPARSE, nil, 0, nil, 0, From d4e640746469ac586f12b400384c4ae7354d9280 Mon Sep 17 00:00:00 2001 From: Nicholas Schwab Date: Fri, 9 Aug 2024 23:14:13 +0200 Subject: [PATCH 228/384] Restrict text files with explicit line feeds to *.go. This partially reverts b732beba6a919b852539bb344b05e25c6a7c3c90. It seems like explicitly setting all files to use line feeds was done due to issues with the go linter, hence it can be restricted to those files (https://github.com/ollama/ollama/pull/6235#issuecomment-2278745953). --- .gitattributes | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/.gitattributes b/.gitattributes index baabd3c13..f1c8bcb4d 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,5 +1,3 @@ llm/ext_server/* linguist-vendored -* text eol=lf -*.png binary -*.ico binary -*.icns binary +* text=auto +*.go text eol=lf From 023451ce471e7781bee65505011c48b9e5541811 Mon Sep 17 00:00:00 2001 From: CognitiveTech Date: Sat, 10 Aug 2024 21:43:08 -0400 Subject: [PATCH 229/384] add integration obook-summary (#6305) --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 7c606e1c8..aae92e6c2 100644 --- a/README.md +++ b/README.md @@ -325,6 +325,7 @@ See the [API documentation](./docs/api.md) for all endpoints. - [tlm](https://github.com/yusufcanb/tlm) - [podman-ollama](https://github.com/ericcurtin/podman-ollama) - [gollama](https://github.com/sammcj/gollama) +- [Ollama eBook Summary](https://github.com/cognitivetech/ollama-ebook-summary/) ### Database From 25906d72d1482bc9dc2e4300a42c8db4823ee1a3 Mon Sep 17 00:00:00 2001 From: Daniel Hiltgen Date: Sun, 11 Aug 2024 11:30:20 -0700 Subject: [PATCH 230/384] llm: prevent loading too large models on windows (#5926) Don't allow loading models that would lead to memory exhaustion (across vram, system memory and disk paging). This check was already applied on Linux but should also be applied on Windows as well. --- llm/server.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/llm/server.go b/llm/server.go index 41736068c..0bd94f355 100644 --- a/llm/server.go +++ b/llm/server.go @@ -125,8 +125,9 @@ func NewLlamaServer(gpus gpu.GpuInfoList, model string, ggml *GGML, adapters, pr } } - // On linux, over-allocating CPU memory will almost always result in an error - if runtime.GOOS == "linux" { + // On linux and windows, over-allocating CPU memory will almost always result in an error + // Darwin has fully dynamic swap so has no direct concept of free swap space + if runtime.GOOS != "darwin" { systemMemoryRequired := estimate.TotalSize - estimate.VRAMSize available := systemFreeMemory + systemSwapFreeMemory if systemMemoryRequired > available { From 15c2d8fe149ba2b58aadbab615a6955f8821c7a9 Mon Sep 17 00:00:00 2001 From: Jeffrey Morgan Date: Sun, 11 Aug 2024 11:57:10 -0700 Subject: [PATCH 231/384] server: parallelize embeddings in API web handler instead of in subprocess runner (#6220) For simplicity, perform parallelization of embedding requests in the API handler instead of offloading this to the subprocess runner. This keeps the scheduling story simpler as it builds on existing parallel requests, similar to existing text completion functionality. --- llm/ext_server/server.cpp | 42 ++++++++------------------------------- llm/server.go | 32 +++++++++++++---------------- server/routes.go | 42 +++++++++++++++++++++++++-------------- server/sched_test.go | 8 ++++---- 4 files changed, 53 insertions(+), 71 deletions(-) diff --git a/llm/ext_server/server.cpp b/llm/ext_server/server.cpp index c65901c7c..5717c17a9 100644 --- a/llm/ext_server/server.cpp +++ b/llm/ext_server/server.cpp @@ -1223,9 +1223,7 @@ struct llama_server_context res.result_json = json { - {"id", res.id}, {"embedding", std::vector(embd, embd + n_embd)}, - {"timings", slot.get_formated_timings()}, }; } } @@ -3194,41 +3192,17 @@ int main(int argc, char **argv) { prompt = ""; } - if (prompt.size() == 1) { - prompt = prompt[0]; - } - // create and queue the task - json responses; - { - const int id_task = llama.queue_tasks.get_new_id(); - llama.queue_results.add_waiting_task_id(id_task); - llama.request_completion(id_task, {{"prompt", prompt}}, true, -1); + const int task_id = llama.queue_tasks.get_new_id(); + llama.queue_results.add_waiting_task_id(task_id); + llama.request_completion(task_id, {{"prompt", prompt}}, true, -1); - // get the result - task_result result = llama.queue_results.recv(id_task); - llama.queue_results.remove_waiting_task_id(id_task); - if (result.error) { - return res.set_content(result.result_json.dump(), "application/json; charset=utf-8"); - } + // get the result + task_result result = llama.queue_results.recv(task_id); + llama.queue_results.remove_waiting_task_id(task_id); - responses = result.result_json.value("results", std::vector{result.result_json}); - std::sort(responses.begin(), responses.end(), [](const json& a, const json& b) { - return a["id"] < b["id"]; - }); - - json embeddings = json::array(); - - int prompt_n = 0; - for (auto & elem : responses) { - embeddings.push_back(elem.at("embedding")); - prompt_n += elem.at("timings").at("prompt_n").get(); - } - - // send the result - json embedding_res = json{{"embedding", embeddings}, {"prompt_n", prompt_n}}; - return res.set_content(embedding_res.dump(), "application/json; charset=utf-8"); - } + // send the result + return res.set_content(result.result_json.dump(), "application/json; charset=utf-8"); }); // GG: if I put the main loop inside a thread, it crashes on the first request when build in Debug!? diff --git a/llm/server.go b/llm/server.go index 0bd94f355..d2b8db9b3 100644 --- a/llm/server.go +++ b/llm/server.go @@ -33,7 +33,7 @@ type LlamaServer interface { Ping(ctx context.Context) error WaitUntilRunning(ctx context.Context) error Completion(ctx context.Context, req CompletionRequest, fn func(CompletionResponse)) error - Embed(ctx context.Context, input []string) (*EmbedResponse, error) + Embedding(ctx context.Context, input string) ([]float32, error) Tokenize(ctx context.Context, content string) ([]int, error) Detokenize(ctx context.Context, tokens []int) (string, error) Close() error @@ -883,24 +883,20 @@ func (s *llmServer) Completion(ctx context.Context, req CompletionRequest, fn fu return nil } -type EmbedRequest struct { - Content []string `json:"content"` +type EmbeddingRequest struct { + Content string `json:"content"` } -type EmbedResponse struct { - Embedding [][]float32 `json:"embedding"` - PromptEvalCount int `json:"prompt_n"` +type EmbeddingResponse struct { + Embedding []float32 `json:"embedding"` } -func (s *llmServer) Embed(ctx context.Context, input []string) (*EmbedResponse, error) { - // each input will use a slot, so we need to acquire the semaphore for - // the number of inputs up to numParallel - slots := int64(min(len(input), s.numParallel)) - if err := s.sem.Acquire(ctx, slots); err != nil { +func (s *llmServer) Embedding(ctx context.Context, input string) ([]float32, error) { + if err := s.sem.Acquire(ctx, 1); err != nil { slog.Error("Failed to acquire semaphore", "error", err) return nil, err } - defer s.sem.Release(slots) + defer s.sem.Release(1) // Make sure the server is ready status, err := s.getServerStatusRetry(ctx) @@ -910,18 +906,18 @@ func (s *llmServer) Embed(ctx context.Context, input []string) (*EmbedResponse, return nil, fmt.Errorf("unexpected server status: %s", status.ToString()) } - data, err := json.Marshal(EmbedRequest{Content: input}) + data, err := json.Marshal(EmbeddingRequest{Content: input}) if err != nil { return nil, fmt.Errorf("error marshaling embed data: %w", err) } - req, err := http.NewRequestWithContext(ctx, http.MethodPost, fmt.Sprintf("http://127.0.0.1:%d/embedding", s.port), bytes.NewBuffer(data)) + r, err := http.NewRequestWithContext(ctx, http.MethodPost, fmt.Sprintf("http://127.0.0.1:%d/embedding", s.port), bytes.NewBuffer(data)) if err != nil { return nil, fmt.Errorf("error creating embed request: %w", err) } - req.Header.Set("Content-Type", "application/json") + r.Header.Set("Content-Type", "application/json") - resp, err := http.DefaultClient.Do(req) + resp, err := http.DefaultClient.Do(r) if err != nil { return nil, fmt.Errorf("do embedding request: %w", err) } @@ -937,12 +933,12 @@ func (s *llmServer) Embed(ctx context.Context, input []string) (*EmbedResponse, return nil, fmt.Errorf("%s", body) } - var e EmbedResponse + var e EmbeddingResponse if err := json.Unmarshal(body, &e); err != nil { return nil, fmt.Errorf("unmarshal tokenize response: %w", err) } - return &e, nil + return e.Embedding, nil } type TokenizeRequest struct { diff --git a/server/routes.go b/server/routes.go index e55eaa9d4..e5a31002f 100644 --- a/server/routes.go +++ b/server/routes.go @@ -23,6 +23,7 @@ import ( "github.com/gin-contrib/cors" "github.com/gin-gonic/gin" + "golang.org/x/sync/errgroup" "github.com/ollama/ollama/api" "github.com/ollama/ollama/envconfig" @@ -346,6 +347,7 @@ func (s *Server) EmbedHandler(c *gin.Context) { return } + var count int for i, s := range input { tokens, err := r.Tokenize(c.Request.Context(), s) if err != nil { @@ -368,25 +370,36 @@ func (s *Server) EmbedHandler(c *gin.Context) { } } + count += len(tokens) + input[i] = s } - embeddings, err := r.Embed(c.Request.Context(), input) - if err != nil { - slog.Error("embedding generation failed", "error", err) - c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to generate embedding"}) - return + + var g errgroup.Group + embeddings := make([][]float32, len(input)) + for i, text := range input { + g.Go(func() error { + embedding, err := r.Embedding(c.Request.Context(), text) + if err != nil { + return err + } + embeddings[i] = normalize(embedding) + return nil + }) } - for i, e := range embeddings.Embedding { - embeddings.Embedding[i] = normalize(e) + if err := g.Wait(); err != nil { + slog.Error("embedding generation failed", "error", err) + c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Errorf("failed to generate embeddings: %v", err)}) + return } resp := api.EmbedResponse{ Model: req.Model, - Embeddings: embeddings.Embedding, + Embeddings: embeddings, TotalDuration: time.Since(checkpointStart), LoadDuration: checkpointLoaded.Sub(checkpointStart), - PromptEvalCount: embeddings.PromptEvalCount, + PromptEvalCount: count, } c.JSON(http.StatusOK, resp) } @@ -430,21 +443,20 @@ func (s *Server) EmbeddingsHandler(c *gin.Context) { return } - embeddings, err := r.Embed(c.Request.Context(), []string{req.Prompt}) + embedding, err := r.Embedding(c.Request.Context(), req.Prompt) if err != nil { slog.Info(fmt.Sprintf("embedding generation failed: %v", err)) c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to generate embedding"}) return } - embedding := make([]float64, len(embeddings.Embedding[0])) - - for i, v := range embeddings.Embedding[0] { - embedding[i] = float64(v) + var e []float64 + for _, v := range embedding { + e = append(e, float64(v)) } resp := api.EmbeddingResponse{ - Embedding: embedding, + Embedding: e, } c.JSON(http.StatusOK, resp) } diff --git a/server/sched_test.go b/server/sched_test.go index c87174309..713b9259e 100644 --- a/server/sched_test.go +++ b/server/sched_test.go @@ -708,8 +708,8 @@ type mockLlm struct { pingResp error waitResp error completionResp error - embedResp *llm.EmbedResponse - embedRespErr error + embeddingResp []float32 + embeddingRespErr error tokenizeResp []int tokenizeRespErr error detokenizeResp string @@ -727,8 +727,8 @@ func (s *mockLlm) Completion(ctx context.Context, req llm.CompletionRequest, fn return s.completionResp } -func (s *mockLlm) Embed(ctx context.Context, input []string) (*llm.EmbedResponse, error) { - return s.embedResp, s.embedRespErr +func (s *mockLlm) Embedding(ctx context.Context, input string) ([]float32, error) { + return s.embeddingResp, s.embeddingRespErr } func (s *mockLlm) Tokenize(ctx context.Context, content string) ([]int, error) { From 8aac22438ef34192ff804dbeb1b5e9a7e180eb7c Mon Sep 17 00:00:00 2001 From: Josh <76125168+joshyan1@users.noreply.github.com> Date: Mon, 12 Aug 2024 09:28:55 -0700 Subject: [PATCH 232/384] server: speed up single gguf creates (#5898) --- server/model.go | 17 +++++++-- server/model_test.go | 82 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 96 insertions(+), 3 deletions(-) diff --git a/server/model.go b/server/model.go index ad6e4e552..8d7ed7e6d 100644 --- a/server/model.go +++ b/server/model.go @@ -176,9 +176,20 @@ func parseFromFile(ctx context.Context, file *os.File, digest string, fn func(ap mediatype = "application/vnd.ollama.image.projector" } - layer, err := NewLayer(io.NewSectionReader(file, offset, n), mediatype) - if err != nil { - return nil, err + var layer *Layer + if digest != "" && n == stat.Size() && offset == 0 { + layer, err = NewLayerFromLayer(digest, mediatype, file.Name()) + if err != nil { + slog.Debug("could not create new layer from layer", "error", err) + } + } + + // Fallback to creating layer from file copy (either NewLayerFromLayer failed, or digest empty/n != stat.Size()) + if layer == nil { + layer, err = NewLayer(io.NewSectionReader(file, offset, n), mediatype) + if err != nil { + return nil, err + } } layers = append(layers, &layerGGML{layer, ggml}) diff --git a/server/model_test.go b/server/model_test.go index aa214d3d3..63fc408d3 100644 --- a/server/model_test.go +++ b/server/model_test.go @@ -2,8 +2,10 @@ package server import ( "bytes" + "context" "encoding/json" "fmt" + "io" "os" "path/filepath" "testing" @@ -11,6 +13,7 @@ import ( "github.com/google/go-cmp/cmp" "github.com/ollama/ollama/api" + "github.com/ollama/ollama/llm" "github.com/ollama/ollama/template" ) @@ -133,3 +136,82 @@ The temperature in San Francisco, CA is 70°F and in Toronto, Canada is 20°C.`, }) } } + +func TestParseFromFileFromLayer(t *testing.T) { + tempModels := t.TempDir() + + file, err := os.CreateTemp(tempModels, "") + if err != nil { + t.Fatalf("failed to open file: %v", err) + } + defer file.Close() + if err := llm.WriteGGUF(file, llm.KV{"general.architecture": "gemma"}, []llm.Tensor{}); err != nil { + t.Fatalf("failed to write gguf: %v", err) + } + + if _, err := file.Seek(0, io.SeekStart); err != nil { + t.Fatalf("failed to seek to start: %v", err) + } + + layers, err := parseFromFile(context.Background(), file, "", func(api.ProgressResponse) {}) + if err != nil { + t.Fatalf("failed to parse from file: %v", err) + } + + if len(layers) != 1 { + t.Fatalf("got %d != want 1", len(layers)) + } + + if _, err := file.Seek(0, io.SeekStart); err != nil { + t.Fatalf("failed to seek to start: %v", err) + } + + layers2, err := parseFromFile(context.Background(), file, layers[0].Digest, func(api.ProgressResponse) {}) + if err != nil { + t.Fatalf("failed to parse from file: %v", err) + } + if len(layers2) != 1 { + t.Fatalf("got %d != want 1", len(layers2)) + } + + if layers[0].Digest != layers2[0].Digest { + t.Fatalf("got %s != want %s", layers[0].Digest, layers2[0].Digest) + } + + if layers[0].Size != layers2[0].Size { + t.Fatalf("got %d != want %d", layers[0].Size, layers2[0].Size) + } + + if layers[0].MediaType != layers2[0].MediaType { + t.Fatalf("got %v != want %v", layers[0].MediaType, layers2[0].MediaType) + } +} + +func TestParseLayerFromCopy(t *testing.T) { + tempModels := t.TempDir() + + file2, err := os.CreateTemp(tempModels, "") + if err != nil { + t.Fatalf("failed to open file: %v", err) + } + defer file2.Close() + + for range 5 { + if err := llm.WriteGGUF(file2, llm.KV{"general.architecture": "gemma"}, []llm.Tensor{}); err != nil { + t.Fatalf("failed to write gguf: %v", err) + } + } + + if _, err := file2.Seek(0, io.SeekStart); err != nil { + t.Fatalf("failed to seek to start: %v", err) + } + + layers, err := parseFromFile(context.Background(), file2, "", func(api.ProgressResponse) {}) + if err != nil { + t.Fatalf("failed to parse from file: %v", err) + } + + if len(layers) != 5 { + t.Fatalf("got %d != want 5", len(layers)) + } +} From 1dc3ef3aa9d451a63fcb6ea2e1b6ea5289a1a325 Mon Sep 17 00:00:00 2001 From: Josh <76125168+joshyan1@users.noreply.github.com> Date: Mon, 12 Aug 2024 09:57:51 -0700 Subject: [PATCH 233/384] Revert "server: speed up single gguf creates (#5898)" (#6323) This reverts commit 8aac22438ef34192ff804dbeb1b5e9a7e180eb7c. --- server/model.go | 17 ++------- server/model_test.go | 82 -------------------------------------------- 2 files changed, 3 insertions(+), 96 deletions(-) diff --git a/server/model.go b/server/model.go index 8d7ed7e6d..ad6e4e552 100644 --- a/server/model.go +++ b/server/model.go @@ -176,20 +176,9 @@ func parseFromFile(ctx context.Context, file *os.File, digest string, fn func(ap mediatype = "application/vnd.ollama.image.projector" } - var layer *Layer - if digest != "" && n == stat.Size() && offset == 0 { - layer, err = NewLayerFromLayer(digest, mediatype, file.Name()) - if err != nil { - slog.Debug("could not create new layer from layer", "error", err) - } - } - - // Fallback to creating layer from file copy (either NewLayerFromLayer failed, or digest empty/n != stat.Size()) - if layer == nil { - layer, err = NewLayer(io.NewSectionReader(file, offset, n), mediatype) - if err != nil { - return nil, err - } + layer, err := NewLayer(io.NewSectionReader(file, offset, n), mediatype) + if err != nil { + return nil, err } layers = append(layers, &layerGGML{layer, ggml}) diff --git a/server/model_test.go b/server/model_test.go index 63fc408d3..aa214d3d3 100644 --- a/server/model_test.go +++ b/server/model_test.go @@ -2,10 +2,8 @@ package server import ( "bytes" - "context" "encoding/json" "fmt" - "io" "os" "path/filepath" "testing" @@ -13,7 +11,6 @@ import ( "github.com/google/go-cmp/cmp" "github.com/ollama/ollama/api" - "github.com/ollama/ollama/llm" "github.com/ollama/ollama/template" ) @@ -136,82 +133,3 @@ The temperature in San Francisco, CA is 70°F and in Toronto, Canada is 20°C.`, }) } } - -func TestParseFromFileFromLayer(t *testing.T) { - tempModels := t.TempDir() - - file, err := os.CreateTemp(tempModels, "") - if err != nil { - t.Fatalf("failed to open file: %v", err) - } - defer file.Close() - if err := llm.WriteGGUF(file, llm.KV{"general.architecture": "gemma"}, []llm.Tensor{}); err != nil { - t.Fatalf("failed to write gguf: %v", err) - } - - if _, err := file.Seek(0, io.SeekStart); err != nil { - t.Fatalf("failed to seek to start: %v", err) - } - - layers, err := parseFromFile(context.Background(), file, "", func(api.ProgressResponse) {}) - if err != nil { - t.Fatalf("failed to parse from file: %v", err) - } - - if len(layers) != 1 { - t.Fatalf("got %d != want 1", len(layers)) - } - - if _, err := file.Seek(0, io.SeekStart); err != nil { - t.Fatalf("failed to seek to start: %v", err) - } - - layers2, err := parseFromFile(context.Background(), file, layers[0].Digest, func(api.ProgressResponse) {}) - if err != nil { - t.Fatalf("failed to parse from file: %v", err) - } - if len(layers2) != 1 { - t.Fatalf("got %d != want 1", len(layers2)) - } - - if layers[0].Digest != layers2[0].Digest { - t.Fatalf("got %s != want %s", layers[0].Digest, layers2[0].Digest) - } - - if layers[0].Size != layers2[0].Size { - t.Fatalf("got %d != want %d", layers[0].Size, layers2[0].Size) - } - - if layers[0].MediaType != layers2[0].MediaType { - t.Fatalf("got %v != want %v", layers[0].MediaType, layers2[0].MediaType) - } -} - -func TestParseLayerFromCopy(t *testing.T) { - tempModels := t.TempDir() - - file2, err := os.CreateTemp(tempModels, "") - if err != nil { - t.Fatalf("failed to open file: %v", err) - } - defer file2.Close() - - for range 5 { - if err := llm.WriteGGUF(file2, llm.KV{"general.architecture": "gemma"}, []llm.Tensor{}); err != nil { - t.Fatalf("failed to write gguf: %v", err) - } - } - - if _, err := file2.Seek(0, io.SeekStart); err != nil { - t.Fatalf("failed to seek to start: %v", err) - } - - layers, err := parseFromFile(context.Background(), file2, "", func(api.ProgressResponse) {}) - if err != nil { - t.Fatalf("failed to parse from file: %v", err) - } - - if len(layers) != 5 { - t.Fatalf("got %d != want 5", len(layers)) - } -} From 01d544d373d0f7782a9da2a830e0e7fa6926a584 Mon Sep 17 00:00:00 2001 From: royjhan <65097070+royjhan@users.noreply.github.com> Date: Mon, 12 Aug 2024 13:33:34 -0400 Subject: [PATCH 234/384] OpenAI: Simplify input output in testing (#5858) * simplify input output * direct comp * in line image * rm error pointer type * update response testing * lint --- openai/openai_test.go | 668 ++++++++++++++++++++++-------------------- 1 file changed, 344 insertions(+), 324 deletions(-) diff --git a/openai/openai_test.go b/openai/openai_test.go index e08a96c92..c7e9f3849 100644 --- a/openai/openai_test.go +++ b/openai/openai_test.go @@ -7,27 +7,22 @@ import ( "io" "net/http" "net/http/httptest" + "reflect" "strings" "testing" "time" "github.com/gin-gonic/gin" - "github.com/stretchr/testify/assert" "github.com/ollama/ollama/api" ) const ( - prefix = `data:image/jpeg;base64,` - image = `iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk+A8AAQUBAScY42YAAAAASUVORK5CYII=` - imageURL = prefix + image + prefix = `data:image/jpeg;base64,` + image = `iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk+A8AAQUBAScY42YAAAAASUVORK5CYII=` ) -func prepareRequest(req *http.Request, body any) { - bodyBytes, _ := json.Marshal(body) - req.Body = io.NopCloser(bytes.NewReader(bodyBytes)) - req.Header.Set("Content-Type", "application/json") -} +var False = false func captureRequestMiddleware(capturedRequest any) gin.HandlerFunc { return func(c *gin.Context) { @@ -43,134 +38,136 @@ func captureRequestMiddleware(capturedRequest any) gin.HandlerFunc { func TestChatMiddleware(t *testing.T) { type testCase struct { - Name string - Setup func(t *testing.T, req *http.Request) - Expected func(t *testing.T, req *api.ChatRequest, resp *httptest.ResponseRecorder) + name string + body string + req api.ChatRequest + err ErrorResponse } var capturedRequest *api.ChatRequest testCases := []testCase{ { - Name: "chat handler", - Setup: func(t *testing.T, req *http.Request) { - body := ChatCompletionRequest{ - Model: "test-model", - Messages: []Message{{Role: "user", Content: "Hello"}}, - } - prepareRequest(req, body) - }, - Expected: func(t *testing.T, req *api.ChatRequest, resp *httptest.ResponseRecorder) { - if resp.Code != http.StatusOK { - t.Fatalf("expected 200, got %d", resp.Code) - } - - if req.Messages[0].Role != "user" { - t.Fatalf("expected 'user', got %s", req.Messages[0].Role) - } - - if req.Messages[0].Content != "Hello" { - t.Fatalf("expected 'Hello', got %s", req.Messages[0].Content) - } + name: "chat handler", + body: `{ + "model": "test-model", + "messages": [ + {"role": "user", "content": "Hello"} + ] + }`, + req: api.ChatRequest{ + Model: "test-model", + Messages: []api.Message{ + { + Role: "user", + Content: "Hello", + }, + }, + Options: map[string]any{ + "temperature": 1.0, + "top_p": 1.0, + }, + Stream: &False, }, }, { - Name: "chat handler with image content", - Setup: func(t *testing.T, req *http.Request) { - body := ChatCompletionRequest{ - Model: "test-model", - Messages: []Message{ - { - Role: "user", Content: []map[string]any{ - {"type": "text", "text": "Hello"}, - {"type": "image_url", "image_url": map[string]string{"url": imageURL}}, + name: "chat handler with image content", + body: `{ + "model": "test-model", + "messages": [ + { + "role": "user", + "content": [ + { + "type": "text", + "text": "Hello" + }, + { + "type": "image_url", + "image_url": { + "url": "` + prefix + image + `" + } + } + ] + } + ] + }`, + req: api.ChatRequest{ + Model: "test-model", + Messages: []api.Message{ + { + Role: "user", + Content: "Hello", + }, + { + Role: "user", + Images: []api.ImageData{ + func() []byte { + img, _ := base64.StdEncoding.DecodeString(image) + return img + }(), + }, + }, + }, + Options: map[string]any{ + "temperature": 1.0, + "top_p": 1.0, + }, + Stream: &False, + }, + }, + { + name: "chat handler with tools", + body: `{ + "model": "test-model", + "messages": [ + {"role": "user", "content": "What's the weather like in Paris Today?"}, + {"role": "assistant", "tool_calls": [{"id": "id", "type": "function", "function": {"name": "get_current_weather", "arguments": "{\"location\": \"Paris, France\", \"format\": \"celsius\"}"}}]} + ] + }`, + req: api.ChatRequest{ + Model: "test-model", + Messages: []api.Message{ + { + Role: "user", + Content: "What's the weather like in Paris Today?", + }, + { + Role: "assistant", + ToolCalls: []api.ToolCall{ + { + Function: api.ToolCallFunction{ + Name: "get_current_weather", + Arguments: map[string]interface{}{ + "location": "Paris, France", + "format": "celsius", + }, + }, }, }, }, - } - prepareRequest(req, body) - }, - Expected: func(t *testing.T, req *api.ChatRequest, resp *httptest.ResponseRecorder) { - if resp.Code != http.StatusOK { - t.Fatalf("expected 200, got %d", resp.Code) - } - - if req.Messages[0].Role != "user" { - t.Fatalf("expected 'user', got %s", req.Messages[0].Role) - } - - if req.Messages[0].Content != "Hello" { - t.Fatalf("expected 'Hello', got %s", req.Messages[0].Content) - } - - img, _ := base64.StdEncoding.DecodeString(imageURL[len(prefix):]) - - if req.Messages[1].Role != "user" { - t.Fatalf("expected 'user', got %s", req.Messages[1].Role) - } - - if !bytes.Equal(req.Messages[1].Images[0], img) { - t.Fatalf("expected image encoding, got %s", req.Messages[1].Images[0]) - } + }, + Options: map[string]any{ + "temperature": 1.0, + "top_p": 1.0, + }, + Stream: &False, }, }, + { - Name: "chat handler with tools", - Setup: func(t *testing.T, req *http.Request) { - body := ChatCompletionRequest{ - Model: "test-model", - Messages: []Message{ - {Role: "user", Content: "What's the weather like in Paris Today?"}, - {Role: "assistant", ToolCalls: []ToolCall{{ - ID: "id", - Type: "function", - Function: struct { - Name string `json:"name"` - Arguments string `json:"arguments"` - }{ - Name: "get_current_weather", - Arguments: "{\"location\": \"Paris, France\", \"format\": \"celsius\"}", - }, - }}}, - }, - } - prepareRequest(req, body) - }, - Expected: func(t *testing.T, req *api.ChatRequest, resp *httptest.ResponseRecorder) { - if resp.Code != 200 { - t.Fatalf("expected 200, got %d", resp.Code) - } - - if req.Messages[0].Content != "What's the weather like in Paris Today?" { - t.Fatalf("expected What's the weather like in Paris Today?, got %s", req.Messages[0].Content) - } - - if req.Messages[1].ToolCalls[0].Function.Arguments["location"] != "Paris, France" { - t.Fatalf("expected 'Paris, France', got %v", req.Messages[1].ToolCalls[0].Function.Arguments["location"]) - } - - if req.Messages[1].ToolCalls[0].Function.Arguments["format"] != "celsius" { - t.Fatalf("expected celsius, got %v", req.Messages[1].ToolCalls[0].Function.Arguments["format"]) - } - }, - }, - { - Name: "chat handler error forwarding", - Setup: func(t *testing.T, req *http.Request) { - body := ChatCompletionRequest{ - Model: "test-model", - Messages: []Message{{Role: "user", Content: 2}}, - } - prepareRequest(req, body) - }, - Expected: func(t *testing.T, req *api.ChatRequest, resp *httptest.ResponseRecorder) { - if resp.Code != http.StatusBadRequest { - t.Fatalf("expected 400, got %d", resp.Code) - } - - if !strings.Contains(resp.Body.String(), "invalid message content type") { - t.Fatalf("error was not forwarded") - } + name: "chat handler error forwarding", + body: `{ + "model": "test-model", + "messages": [ + {"role": "user", "content": 2} + ] + }`, + err: ErrorResponse{ + Error: Error{ + Message: "invalid message content type: float64", + Type: "invalid_request_error", + }, }, }, } @@ -185,16 +182,26 @@ func TestChatMiddleware(t *testing.T) { router.Handle(http.MethodPost, "/api/chat", endpoint) for _, tc := range testCases { - t.Run(tc.Name, func(t *testing.T) { - req, _ := http.NewRequest(http.MethodPost, "/api/chat", nil) - - tc.Setup(t, req) + t.Run(tc.name, func(t *testing.T) { + req, _ := http.NewRequest(http.MethodPost, "/api/chat", strings.NewReader(tc.body)) + req.Header.Set("Content-Type", "application/json") resp := httptest.NewRecorder() router.ServeHTTP(resp, req) - tc.Expected(t, capturedRequest, resp) + var errResp ErrorResponse + if resp.Code != http.StatusOK { + if err := json.Unmarshal(resp.Body.Bytes(), &errResp); err != nil { + t.Fatal(err) + } + } + if capturedRequest != nil && !reflect.DeepEqual(tc.req, *capturedRequest) { + t.Fatal("requests did not match") + } + if !reflect.DeepEqual(tc.err, errResp) { + t.Fatal("errors did not match") + } capturedRequest = nil }) } @@ -202,71 +209,52 @@ func TestChatMiddleware(t *testing.T) { func TestCompletionsMiddleware(t *testing.T) { type testCase struct { - Name string - Setup func(t *testing.T, req *http.Request) - Expected func(t *testing.T, req *api.GenerateRequest, resp *httptest.ResponseRecorder) + name string + body string + req api.GenerateRequest + err ErrorResponse } var capturedRequest *api.GenerateRequest testCases := []testCase{ { - Name: "completions handler", - Setup: func(t *testing.T, req *http.Request) { - temp := float32(0.8) - body := CompletionRequest{ - Model: "test-model", - Prompt: "Hello", - Temperature: &temp, - Stop: []string{"\n", "stop"}, - Suffix: "suffix", - } - prepareRequest(req, body) - }, - Expected: func(t *testing.T, req *api.GenerateRequest, resp *httptest.ResponseRecorder) { - if req.Prompt != "Hello" { - t.Fatalf("expected 'Hello', got %s", req.Prompt) - } - - if req.Options["temperature"] != 1.6 { - t.Fatalf("expected 1.6, got %f", req.Options["temperature"]) - } - - stopTokens, ok := req.Options["stop"].([]any) - - if !ok { - t.Fatalf("expected stop tokens to be a list") - } - - if stopTokens[0] != "\n" || stopTokens[1] != "stop" { - t.Fatalf("expected ['\\n', 'stop'], got %v", stopTokens) - } - - if req.Suffix != "suffix" { - t.Fatalf("expected 'suffix', got %s", req.Suffix) - } + name: "completions handler", + body: `{ + "model": "test-model", + "prompt": "Hello", + "temperature": 0.8, + "stop": ["\n", "stop"], + "suffix": "suffix" + }`, + req: api.GenerateRequest{ + Model: "test-model", + Prompt: "Hello", + Options: map[string]any{ + "frequency_penalty": 0.0, + "presence_penalty": 0.0, + "temperature": 1.6, + "top_p": 1.0, + "stop": []any{"\n", "stop"}, + }, + Suffix: "suffix", + Stream: &False, }, }, { - Name: "completions handler error forwarding", - Setup: func(t *testing.T, req *http.Request) { - body := CompletionRequest{ - Model: "test-model", - Prompt: "Hello", - Temperature: nil, - Stop: []int{1, 2}, - Suffix: "suffix", - } - prepareRequest(req, body) - }, - Expected: func(t *testing.T, req *api.GenerateRequest, resp *httptest.ResponseRecorder) { - if resp.Code != http.StatusBadRequest { - t.Fatalf("expected 400, got %d", resp.Code) - } - - if !strings.Contains(resp.Body.String(), "invalid type for 'stop' field") { - t.Fatalf("error was not forwarded") - } + name: "completions handler error forwarding", + body: `{ + "model": "test-model", + "prompt": "Hello", + "temperature": null, + "stop": [1, 2], + "suffix": "suffix" + }`, + err: ErrorResponse{ + Error: Error{ + Message: "invalid type for 'stop' field: float64", + Type: "invalid_request_error", + }, }, }, } @@ -281,15 +269,27 @@ func TestCompletionsMiddleware(t *testing.T) { router.Handle(http.MethodPost, "/api/generate", endpoint) for _, tc := range testCases { - t.Run(tc.Name, func(t *testing.T) { - req, _ := http.NewRequest(http.MethodPost, "/api/generate", nil) - - tc.Setup(t, req) + t.Run(tc.name, func(t *testing.T) { + req, _ := http.NewRequest(http.MethodPost, "/api/generate", strings.NewReader(tc.body)) + req.Header.Set("Content-Type", "application/json") resp := httptest.NewRecorder() router.ServeHTTP(resp, req) - tc.Expected(t, capturedRequest, resp) + var errResp ErrorResponse + if resp.Code != http.StatusOK { + if err := json.Unmarshal(resp.Body.Bytes(), &errResp); err != nil { + t.Fatal(err) + } + } + + if capturedRequest != nil && !reflect.DeepEqual(tc.req, *capturedRequest) { + t.Fatal("requests did not match") + } + + if !reflect.DeepEqual(tc.err, errResp) { + t.Fatal("errors did not match") + } capturedRequest = nil }) @@ -298,78 +298,47 @@ func TestCompletionsMiddleware(t *testing.T) { func TestEmbeddingsMiddleware(t *testing.T) { type testCase struct { - Name string - Setup func(t *testing.T, req *http.Request) - Expected func(t *testing.T, req *api.EmbedRequest, resp *httptest.ResponseRecorder) + name string + body string + req api.EmbedRequest + err ErrorResponse } var capturedRequest *api.EmbedRequest testCases := []testCase{ { - Name: "embed handler single input", - Setup: func(t *testing.T, req *http.Request) { - body := EmbedRequest{ - Input: "Hello", - Model: "test-model", - } - prepareRequest(req, body) - }, - Expected: func(t *testing.T, req *api.EmbedRequest, resp *httptest.ResponseRecorder) { - if req.Input != "Hello" { - t.Fatalf("expected 'Hello', got %s", req.Input) - } - - if req.Model != "test-model" { - t.Fatalf("expected 'test-model', got %s", req.Model) - } + name: "embed handler single input", + body: `{ + "input": "Hello", + "model": "test-model" + }`, + req: api.EmbedRequest{ + Input: "Hello", + Model: "test-model", }, }, { - Name: "embed handler batch input", - Setup: func(t *testing.T, req *http.Request) { - body := EmbedRequest{ - Input: []string{"Hello", "World"}, - Model: "test-model", - } - prepareRequest(req, body) - }, - Expected: func(t *testing.T, req *api.EmbedRequest, resp *httptest.ResponseRecorder) { - input, ok := req.Input.([]any) - - if !ok { - t.Fatalf("expected input to be a list") - } - - if input[0].(string) != "Hello" { - t.Fatalf("expected 'Hello', got %s", input[0]) - } - - if input[1].(string) != "World" { - t.Fatalf("expected 'World', got %s", input[1]) - } - - if req.Model != "test-model" { - t.Fatalf("expected 'test-model', got %s", req.Model) - } + name: "embed handler batch input", + body: `{ + "input": ["Hello", "World"], + "model": "test-model" + }`, + req: api.EmbedRequest{ + Input: []any{"Hello", "World"}, + Model: "test-model", }, }, { - Name: "embed handler error forwarding", - Setup: func(t *testing.T, req *http.Request) { - body := EmbedRequest{ - Model: "test-model", - } - prepareRequest(req, body) - }, - Expected: func(t *testing.T, req *api.EmbedRequest, resp *httptest.ResponseRecorder) { - if resp.Code != http.StatusBadRequest { - t.Fatalf("expected 400, got %d", resp.Code) - } - - if !strings.Contains(resp.Body.String(), "invalid input") { - t.Fatalf("error was not forwarded") - } + name: "embed handler error forwarding", + body: `{ + "model": "test-model" + }`, + err: ErrorResponse{ + Error: Error{ + Message: "invalid input", + Type: "invalid_request_error", + }, }, }, } @@ -384,116 +353,167 @@ func TestEmbeddingsMiddleware(t *testing.T) { router.Handle(http.MethodPost, "/api/embed", endpoint) for _, tc := range testCases { - t.Run(tc.Name, func(t *testing.T) { - req, _ := http.NewRequest(http.MethodPost, "/api/embed", nil) - - tc.Setup(t, req) + t.Run(tc.name, func(t *testing.T) { + req, _ := http.NewRequest(http.MethodPost, "/api/embed", strings.NewReader(tc.body)) + req.Header.Set("Content-Type", "application/json") resp := httptest.NewRecorder() router.ServeHTTP(resp, req) - tc.Expected(t, capturedRequest, resp) + var errResp ErrorResponse + if resp.Code != http.StatusOK { + if err := json.Unmarshal(resp.Body.Bytes(), &errResp); err != nil { + t.Fatal(err) + } + } + + if capturedRequest != nil && !reflect.DeepEqual(tc.req, *capturedRequest) { + t.Fatal("requests did not match") + } + + if !reflect.DeepEqual(tc.err, errResp) { + t.Fatal("errors did not match") + } capturedRequest = nil }) } } -func TestMiddlewareResponses(t *testing.T) { +func TestListMiddleware(t *testing.T) { type testCase struct { - Name string - Method string - Path string - TestPath string - Handler func() gin.HandlerFunc - Endpoint func(c *gin.Context) - Setup func(t *testing.T, req *http.Request) - Expected func(t *testing.T, resp *httptest.ResponseRecorder) + name string + endpoint func(c *gin.Context) + resp string } testCases := []testCase{ { - Name: "list handler", - Method: http.MethodGet, - Path: "/api/tags", - TestPath: "/api/tags", - Handler: ListMiddleware, - Endpoint: func(c *gin.Context) { + name: "list handler", + endpoint: func(c *gin.Context) { c.JSON(http.StatusOK, api.ListResponse{ Models: []api.ListModelResponse{ { - Name: "Test Model", + Name: "test-model", + ModifiedAt: time.Unix(int64(1686935002), 0).UTC(), }, }, }) }, - Expected: func(t *testing.T, resp *httptest.ResponseRecorder) { - var listResp ListCompletion - if err := json.NewDecoder(resp.Body).Decode(&listResp); err != nil { - t.Fatal(err) - } - - if listResp.Object != "list" { - t.Fatalf("expected list, got %s", listResp.Object) - } - - if len(listResp.Data) != 1 { - t.Fatalf("expected 1, got %d", len(listResp.Data)) - } - - if listResp.Data[0].Id != "Test Model" { - t.Fatalf("expected Test Model, got %s", listResp.Data[0].Id) - } - }, + resp: `{ + "object": "list", + "data": [ + { + "id": "test-model", + "object": "model", + "created": 1686935002, + "owned_by": "library" + } + ] + }`, }, { - Name: "retrieve model", - Method: http.MethodGet, - Path: "/api/show/:model", - TestPath: "/api/show/test-model", - Handler: RetrieveMiddleware, - Endpoint: func(c *gin.Context) { - c.JSON(http.StatusOK, api.ShowResponse{ - ModifiedAt: time.Date(2024, 6, 17, 13, 45, 0, 0, time.UTC), - }) - }, - Expected: func(t *testing.T, resp *httptest.ResponseRecorder) { - var retrieveResp Model - if err := json.NewDecoder(resp.Body).Decode(&retrieveResp); err != nil { - t.Fatal(err) - } - - if retrieveResp.Object != "model" { - t.Fatalf("Expected object to be model, got %s", retrieveResp.Object) - } - - if retrieveResp.Id != "test-model" { - t.Fatalf("Expected id to be test-model, got %s", retrieveResp.Id) - } + name: "list handler empty output", + endpoint: func(c *gin.Context) { + c.JSON(http.StatusOK, api.ListResponse{}) }, + resp: `{ + "object": "list", + "data": null + }`, }, } gin.SetMode(gin.TestMode) - router := gin.New() for _, tc := range testCases { - t.Run(tc.Name, func(t *testing.T) { - router = gin.New() - router.Use(tc.Handler()) - router.Handle(tc.Method, tc.Path, tc.Endpoint) - req, _ := http.NewRequest(tc.Method, tc.TestPath, nil) + router := gin.New() + router.Use(ListMiddleware()) + router.Handle(http.MethodGet, "/api/tags", tc.endpoint) + req, _ := http.NewRequest(http.MethodGet, "/api/tags", nil) - if tc.Setup != nil { - tc.Setup(t, req) - } + resp := httptest.NewRecorder() + router.ServeHTTP(resp, req) - resp := httptest.NewRecorder() - router.ServeHTTP(resp, req) + var expected, actual map[string]any + err := json.Unmarshal([]byte(tc.resp), &expected) + if err != nil { + t.Fatalf("failed to unmarshal expected response: %v", err) + } - assert.Equal(t, http.StatusOK, resp.Code) + err = json.Unmarshal(resp.Body.Bytes(), &actual) + if err != nil { + t.Fatalf("failed to unmarshal actual response: %v", err) + } - tc.Expected(t, resp) - }) + if !reflect.DeepEqual(expected, actual) { + t.Errorf("responses did not match\nExpected: %+v\nActual: %+v", expected, actual) + } + } +} + +func TestRetrieveMiddleware(t *testing.T) { + type testCase struct { + name string + endpoint func(c *gin.Context) + resp string + } + + testCases := []testCase{ + { + name: "retrieve handler", + endpoint: func(c *gin.Context) { + c.JSON(http.StatusOK, api.ShowResponse{ + ModifiedAt: time.Unix(int64(1686935002), 0).UTC(), + }) + }, + resp: `{ + "id":"test-model", + "object":"model", + "created":1686935002, + "owned_by":"library"} + `, + }, + { + name: "retrieve handler error forwarding", + endpoint: func(c *gin.Context) { + c.JSON(http.StatusBadRequest, gin.H{"error": "model not found"}) + }, + resp: `{ + "error": { + "code": null, + "message": "model not found", + "param": null, + "type": "api_error" + } + }`, + }, + } + + gin.SetMode(gin.TestMode) + + for _, tc := range testCases { + router := gin.New() + router.Use(RetrieveMiddleware()) + router.Handle(http.MethodGet, "/api/show/:model", tc.endpoint) + req, _ := http.NewRequest(http.MethodGet, "/api/show/test-model", nil) + + resp := httptest.NewRecorder() + router.ServeHTTP(resp, req) + + var expected, actual map[string]any + err := json.Unmarshal([]byte(tc.resp), &expected) + if err != nil { + t.Fatalf("failed to unmarshal expected response: %v", err) + } + + err = json.Unmarshal(resp.Body.Bytes(), &actual) + if err != nil { + t.Fatalf("failed to unmarshal actual response: %v", err) + } + + if !reflect.DeepEqual(expected, actual) { + t.Errorf("responses did not match\nExpected: %+v\nActual: %+v", expected, actual) + } } } From 980dd15f81e9021c5165a1e516748d42cf134339 Mon Sep 17 00:00:00 2001 From: Josh <76125168+joshyan1@users.noreply.github.com> Date: Mon, 12 Aug 2024 11:46:09 -0700 Subject: [PATCH 235/384] cmd: speed up gguf creates (#6324) --- server/model.go | 17 +++++++-- server/model_test.go | 82 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 96 insertions(+), 3 deletions(-) diff --git a/server/model.go b/server/model.go index ad6e4e552..b17bf0e34 100644 --- a/server/model.go +++ b/server/model.go @@ -176,9 +176,20 @@ func parseFromFile(ctx context.Context, file *os.File, digest string, fn func(ap mediatype = "application/vnd.ollama.image.projector" } - layer, err := NewLayer(io.NewSectionReader(file, offset, n), mediatype) - if err != nil { - return nil, err + var layer Layer + if digest != "" && n == stat.Size() && offset == 0 { + layer, err = NewLayerFromLayer(digest, mediatype, file.Name()) + if err != nil { + slog.Debug("could not create new layer from layer", "error", err) + } + } + + // Fallback to creating layer from file copy (either NewLayerFromLayer failed, or digest empty/n != stat.Size()) + if layer.Digest == "" { + layer, err = NewLayer(io.NewSectionReader(file, offset, n), mediatype) + if err != nil { + return nil, err + } } layers = append(layers, &layerGGML{layer, ggml}) diff --git a/server/model_test.go b/server/model_test.go index aa214d3d3..63fc408d3 100644 --- a/server/model_test.go +++ b/server/model_test.go @@ -2,8 +2,10 @@ package server import ( "bytes" + "context" "encoding/json" "fmt" + "io" "os" "path/filepath" "testing" @@ -11,6 +13,7 @@ import ( "github.com/google/go-cmp/cmp" "github.com/ollama/ollama/api" + "github.com/ollama/ollama/llm" "github.com/ollama/ollama/template" ) @@ -133,3 +136,82 @@ The temperature in San Francisco, CA is 70°F and in Toronto, Canada is 20°C.`, }) } } + +func TestParseFromFileFromLayer(t *testing.T) { + tempModels := t.TempDir() + + file, err := os.CreateTemp(tempModels, "") + if err != nil { + t.Fatalf("failed to open file: %v", err) + } + defer file.Close() + if err := llm.WriteGGUF(file, llm.KV{"general.architecture": "gemma"}, []llm.Tensor{}); err != nil { + t.Fatalf("failed to write gguf: %v", err) + } + + if _, err := file.Seek(0, io.SeekStart); err != nil { + t.Fatalf("failed to seek to start: %v", err) + } + + layers, err := parseFromFile(context.Background(), file, "", func(api.ProgressResponse) {}) + if err != nil { + t.Fatalf("failed to parse from file: %v", err) + } + + if len(layers) != 1 { + t.Fatalf("got %d != want 1", len(layers)) + } + + if _, err := file.Seek(0, io.SeekStart); err != nil { + t.Fatalf("failed to seek to start: %v", err) + } + + layers2, err := parseFromFile(context.Background(), file, layers[0].Digest, func(api.ProgressResponse) {}) + if err != nil { + t.Fatalf("failed to parse from file: %v", err) + } + if len(layers2) != 1 { + t.Fatalf("got %d != want 1", len(layers2)) + } + + if layers[0].Digest != layers2[0].Digest { + t.Fatalf("got %s != want %s", layers[0].Digest, layers2[0].Digest) + } + + if layers[0].Size != layers2[0].Size { + t.Fatalf("got %d != want %d", layers[0].Size, layers2[0].Size) + } + + if layers[0].MediaType != layers2[0].MediaType { + t.Fatalf("got %v != want %v", layers[0].MediaType, layers2[0].MediaType) + } +} + +func TestParseLayerFromCopy(t *testing.T) { + tempModels := t.TempDir() + + file2, err := os.CreateTemp(tempModels, "") + if err != nil { + t.Fatalf("failed to open file: %v", err) + } + defer file2.Close() + + for range 5 { + if err := llm.WriteGGUF(file2, llm.KV{"general.architecture": "gemma"}, []llm.Tensor{}); err != nil { + t.Fatalf("failed to write gguf: %v", err) + } + } + + if _, err := file2.Seek(0, io.SeekStart); err != nil { + t.Fatalf("failed to seek to start: %v", err) + } + + layers, err := parseFromFile(context.Background(), file2, "", func(api.ProgressResponse) {}) + if err != nil { + t.Fatalf("failed to parse from file: %v", err) + } + + if len(layers) != 5 { + t.Fatalf("got %d != want 5", len(layers)) + } +} From f7e3b9190f7e8f99bac8af432b9539e24cd3b57e Mon Sep 17 00:00:00 2001 From: Josh <76125168+joshyan1@users.noreply.github.com> Date: Mon, 12 Aug 2024 11:46:32 -0700 Subject: [PATCH 236/384] cmd: spinner progress for transfer model data (#6100) --- cmd/cmd.go | 45 ++++++++++++++++++++++++++++++++++++++++++--- progress/spinner.go | 14 ++++++++++---- 2 files changed, 52 insertions(+), 7 deletions(-) diff --git a/cmd/cmd.go b/cmd/cmd.go index d47db65b3..2356110ee 100644 --- a/cmd/cmd.go +++ b/cmd/cmd.go @@ -22,6 +22,7 @@ import ( "runtime" "slices" "strings" + "sync/atomic" "syscall" "time" @@ -78,6 +79,7 @@ func CreateHandler(cmd *cobra.Command, args []string) error { status := "transferring model data" spinner := progress.NewSpinner(status) p.Add(status, spinner) + defer p.Stop() for i := range modelfile.Commands { switch modelfile.Commands[i].Name { @@ -112,7 +114,7 @@ func CreateHandler(cmd *cobra.Command, args []string) error { path = tempfile } - digest, err := createBlob(cmd, client, path) + digest, err := createBlob(cmd, client, path, spinner) if err != nil { return err } @@ -263,13 +265,20 @@ func tempZipFiles(path string) (string, error) { return tempfile.Name(), nil } -func createBlob(cmd *cobra.Command, client *api.Client, path string) (string, error) { +func createBlob(cmd *cobra.Command, client *api.Client, path string, spinner *progress.Spinner) (string, error) { bin, err := os.Open(path) if err != nil { return "", err } defer bin.Close() + // Get file info to retrieve the size + fileInfo, err := bin.Stat() + if err != nil { + return "", err + } + fileSize := fileInfo.Size() + hash := sha256.New() if _, err := io.Copy(hash, bin); err != nil { return "", err @@ -279,13 +288,43 @@ func createBlob(cmd *cobra.Command, client *api.Client, path string) (string, er return "", err } + var pw progressWriter + status := "transferring model data 0%" + spinner.SetMessage(status) + + done := make(chan struct{}) + defer close(done) + + go func() { + ticker := time.NewTicker(60 * time.Millisecond) + defer ticker.Stop() + for { + select { + case <-ticker.C: + spinner.SetMessage(fmt.Sprintf("transferring model data %d%%", int(100*pw.n.Load()/fileSize))) + case <-done: + spinner.SetMessage("transferring model data 100%") + return + } + } + }() + digest := fmt.Sprintf("sha256:%x", hash.Sum(nil)) - if err = client.CreateBlob(cmd.Context(), digest, bin); err != nil { + if err = client.CreateBlob(cmd.Context(), digest, io.TeeReader(bin, &pw)); err != nil { return "", err } return digest, nil } +type progressWriter struct { + n atomic.Int64 +} + +func (w *progressWriter) Write(p []byte) (n int, err error) { + w.n.Add(int64(len(p))) + return len(p), nil +} + func RunHandler(cmd *cobra.Command, args []string) error { interactive := true diff --git a/progress/spinner.go b/progress/spinner.go index 02f3f9fb1..e39a45eef 100644 --- a/progress/spinner.go +++ b/progress/spinner.go @@ -3,11 +3,12 @@ package progress import ( "fmt" "strings" + "sync/atomic" "time" ) type Spinner struct { - message string + message atomic.Value messageWidth int parts []string @@ -21,20 +22,25 @@ type Spinner struct { func NewSpinner(message string) *Spinner { s := &Spinner{ - message: message, parts: []string{ "⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏", }, started: time.Now(), } + s.SetMessage(message) go s.start() return s } +func (s *Spinner) SetMessage(message string) { + s.message.Store(message) +} + func (s *Spinner) String() string { var sb strings.Builder - if len(s.message) > 0 { - message := strings.TrimSpace(s.message) + + if message, ok := s.message.Load().(string); ok && len(message) > 0 { + message := strings.TrimSpace(message) if s.messageWidth > 0 && len(message) > s.messageWidth { message = message[:s.messageWidth] } From 6ffb5cb017a1c81970ac637907a8ba6fd151e0e7 Mon Sep 17 00:00:00 2001 From: Michael Yang Date: Mon, 3 Jun 2024 15:53:58 -0700 Subject: [PATCH 237/384] add conversion for microsoft phi 3 mini/medium 4k, 128 --- convert/convert.go | 6 + convert/convert_llama.go | 4 - convert/convert_phi3.go | 125 ++++++++++ convert/convert_test.go | 2 + .../testdata/Phi-3-mini-128k-instruct.json | 225 ++++++++++++++++++ llm/ggml.go | 8 + llm/gguf.go | 15 +- 7 files changed, 373 insertions(+), 12 deletions(-) create mode 100644 convert/convert_phi3.go create mode 100644 convert/testdata/Phi-3-mini-128k-instruct.json diff --git a/convert/convert.go b/convert/convert.go index b9461e4fe..24c19aa4d 100644 --- a/convert/convert.go +++ b/convert/convert.go @@ -27,6 +27,10 @@ func (Parameters) KV(t *Tokenizer) llm.KV { "tokenizer.ggml.token_type": t.Vocabulary.Types, } + if len(t.Merges) > 0 { + kv["tokenizer.ggml.merges"] = t.Merges + } + if t.Template != "" { kv["tokenizer.chat_template"] = t.Template } @@ -89,6 +93,8 @@ func Convert(fsys fs.FS, ws io.WriteSeeker) error { conv = &mixtral{} case "GemmaForCausalLM": conv = &gemma{} + case "Phi3ForCausalLM": + conv = &phi3{} default: return errors.New("unsupported architecture") } diff --git a/convert/convert_llama.go b/convert/convert_llama.go index 0383a85eb..178b13f33 100644 --- a/convert/convert_llama.go +++ b/convert/convert_llama.go @@ -90,10 +90,6 @@ func (p *llama) KV(t *Tokenizer) llm.KV { kv["llama.attention.value_length"] = p.HeadDim } - if len(t.Merges) > 0 { - kv["tokenizer.ggml.merges"] = t.Merges - } - return kv } diff --git a/convert/convert_phi3.go b/convert/convert_phi3.go new file mode 100644 index 000000000..7aa3ed150 --- /dev/null +++ b/convert/convert_phi3.go @@ -0,0 +1,125 @@ +package convert + +import ( + "cmp" + "encoding/binary" + "io" + "math" + "strings" + "sync" + + "github.com/ollama/ollama/llm" +) + +type phi3 struct { + Parameters + NumHiddenLayers uint32 `json:"num_hidden_layers"` + NLayers uint32 `json:"n_layers"` + HiddenSize uint32 `json:"hidden_size"` + NEmbd uint32 `json:"n_embd"` + IntermediateSize uint32 `json:"intermediate_size"` + NumAttentionHeads uint32 `json:"num_attention_heads"` + NHead uint32 `json:"n_head"` + NumKeyValueHeads uint32 `json:"num_key_value_heads"` + NHeadKV uint32 `json:"n_head_kv"` + RopeTheta float32 `json:"rope_theta"` + RopeScaling struct { + Type string `json:"type"` + LongFactor ropeFactor `json:"long_factor"` + ShortFactor ropeFactor `json:"short_factor"` + } `json:"rope_scaling"` + RMSNormEPS float32 `json:"rms_norm_eps"` + NPositions uint32 `json:"n_positions"` + MaxPositionEmbeddings uint32 `json:"max_position_embeddings"` + OriginalMaxPositionEmbeddings uint32 `json:"original_max_position_embeddings"` + SlidingWindow uint32 `json:"sliding_window"` +} + +var _ Converter = (*phi3)(nil) + +func (p *phi3) KV(t *Tokenizer) llm.KV { + kv := p.Parameters.KV(t) + kv["general.architecture"] = "phi3" + kv["general.name"] = "phi3" + kv["phi3.context_length"] = p.MaxPositionEmbeddings + kv["phi3.embedding_length"] = cmp.Or(p.HiddenSize, p.NEmbd) + kv["phi3.feed_forward_length"] = p.IntermediateSize + kv["phi3.block_count"] = cmp.Or(p.NumHiddenLayers, p.NLayers) + kv["phi3.attention.head_count"] = cmp.Or(p.NumAttentionHeads, p.NHead) + kv["phi3.attention.head_count_kv"] = cmp.Or(p.NumKeyValueHeads, p.NHeadKV) + kv["phi3.attention.layer_norm_rms_epsilon"] = p.RMSNormEPS + kv["phi3.rope.dimension_count"] = p.HiddenSize / cmp.Or(p.NumAttentionHeads, p.NHead) + kv["phi3.rope.freq_base"] = p.RopeTheta + kv["phi3.rope.scaling.original_context_length"] = p.OriginalMaxPositionEmbeddings + kv["phi3.attention.sliding_window"] = p.SlidingWindow + + scale := float64(p.MaxPositionEmbeddings) / float64(p.OriginalMaxPositionEmbeddings) + + switch p.RopeScaling.Type { + case "": + // no scaling + case "su": + kv["phi3.rope.scaling.attn_factor"] = float32(max(math.Sqrt(1+math.Log(scale)/math.Log(float64(p.OriginalMaxPositionEmbeddings))), 1.0)) + case "yarn": + kv["phi3.rope.scaling.attn_factor"] = float32(max(0.1*math.Log(scale)+1.0, 1.0)) + default: + panic("unknown rope scaling type") + } + + return kv +} + +func (p *phi3) Tensors(ts []Tensor) []llm.Tensor { + var addRopeFactors sync.Once + + out := make([]llm.Tensor, 0, len(ts)+2) + for _, t := range ts { + name := p.tensorName(t.Name()) + if strings.HasPrefix(name, "blk.0.") { + addRopeFactors.Do(func() { + out = append(out, llm.Tensor{ + Name: "rope_factors_long.weight", + Kind: 0, + Shape: []uint64{uint64(len(p.RopeScaling.LongFactor))}, + WriterTo: p.RopeScaling.LongFactor, + }, llm.Tensor{ + Name: "rope_factors_short.weight", + Kind: 0, + Shape: []uint64{uint64(len(p.RopeScaling.ShortFactor))}, + WriterTo: p.RopeScaling.ShortFactor, + }) + }) + } + + out = append(out, llm.Tensor{ + Name: name, + Kind: t.Kind(), + Shape: t.Shape(), + WriterTo: t, + }) + } + + return out +} + +func (p *phi3) tensorName(n string) string { + return strings.NewReplacer( + "lm_head", "output", + "model.embed_tokens", "token_embd", + "model.norm", "output_norm", + "model.layers", "blk", + "input_layernorm", "attn_norm", + "self_attn.qkv_proj", "attn_qkv", + "self_attn.o_proj", "attn_output", + "mlp.down_proj", "ffn_down", + "mlp.gate_up_proj", "ffn_up", + "post_attention_layernorm", "ffn_norm", + ).Replace(n) +} + +type ropeFactor []float32 + +func (r ropeFactor) WriteTo(w io.Writer) (int64, error) { + err := binary.Write(w, binary.LittleEndian, r) + return 0, err +} diff --git a/convert/convert_test.go b/convert/convert_test.go index 88f38494e..cb2c585ef 100644 --- a/convert/convert_test.go +++ b/convert/convert_test.go @@ -65,6 +65,8 @@ func TestConvertFull(t *testing.T) { "Mistral-7B-Instruct-v0.2", "Mixtral-8x7B-Instruct-v0.1", "gemma-2b-it", + // microsoft/Phi-3-mini-128-instruct@d548c233192db00165d842bf8edff054bb3212f8 + "Phi-3-mini-128k-instruct", } for i := range cases { diff --git a/convert/testdata/Phi-3-mini-128k-instruct.json b/convert/testdata/Phi-3-mini-128k-instruct.json new file mode 100644 index 000000000..19296f5a9 --- /dev/null +++ b/convert/testdata/Phi-3-mini-128k-instruct.json @@ -0,0 +1,225 @@ +{ + "general.architecture": "phi3", + "general.file_type": "1", + "general.quantization_version": "2", + "phi3.block_count": "32", + "phi3.context_length": "131072", + "phi3.embedding_length": "3072", + "phi3.feed_forward_length": "8192", + "phi3.rope.scaling.original_context_length": "4096", + "phi3.rope.dimension_count": "96", + "phi3.rope.freq_base": "10000", + "phi3.rope.scaling.attn_factor": "1.1902381", + "phi3.attention.head_count": "32", + "phi3.attention.head_count_kv": "32", + "phi3.attention.layer_norm_rms_epsilon": "1e-05", + "phi3.attention.sliding_window": "262144", + "tokenizer.ggml.model": "llama", + "tokenizer.ggml.pre": "default", + "tokenizer.ggml.add_bos_token": "false", + "tokenizer.ggml.add_eos_token": "false", + "tokenizer.ggml.bos_token_id": "1", + "tokenizer.ggml.eos_token_id": "32000", + "tokenizer.ggml.unknown_token_id": "0", + "tokenizer.ggml.padding_token_id": "32000", + "tokenizer.ggml.scores": "6e37bcde2adc7e350e87c496eddd7a2124329c1dc66c5bf3ad3997253e4f7a62", + "tokenizer.ggml.token_type": "b6ecf55ec64ee67d87750bdb8d757a2c58bf78377e9f4219f5689a6c4dea57ce", + "tokenizer.ggml.tokens": "d168da3ddd3eee820916945fcb9baf24dd3cde42f606cffa2d19e7c8a8743918", + "blk.0.attn_norm.weight": "216aeb2c9e0c271f899e1ef2a63cceeb8f41e97642e84fada54b1d3c1c11cf25", + "blk.0.attn_output.weight": "b597d56f7188ffc1fafc273fadc59d41738cffd677ae98c61a62c3285b3a3099", + "blk.0.attn_qkv.weight": "d28a6b44e13f59be5483e4be2bedb544e346168d720aca27f47d1a5a722be91e", + "blk.0.ffn_down.weight": "4a691370e5a61fcbbf540fbcbf4c0f1d15dec0364528c0e916d0744f6262b63b", + "blk.0.ffn_norm.weight": "0c00af2b4a3128bec64a0cbb1084b042fdbe13d9ad0d03bd577f9449dfead338", + "blk.0.ffn_up.weight": "b32b52f790c1c083bfb8a3126dc1111cfeeb28dc8c584a930a1e5334cb176bf4", + "blk.1.attn_norm.weight": "68748011503c6c029e8e69a84a8e5a89338f378769627b6dbf7f93d715c292e1", + "blk.1.attn_output.weight": "2267344add13b048ca59e4377c86dc512be8046a57156901fa32a20fa74e4ee0", + "blk.1.attn_qkv.weight": "9109d2e3d7a2eacfda5226587b8be124a3bf44b972da7ebb17aa15795897eacc", + "blk.1.ffn_down.weight": "d675df4df4dd039c0c339ad6445d39eddd2004db6bf35bed6314c7497245a633", + "blk.1.ffn_norm.weight": "3b5767ae977bc8baaa06b06efdbea193b6b3ba605ce76d77a76ce317e935500c", + "blk.1.ffn_up.weight": "80dfd6d9d234b00334c89b8e0a02f81899c2efd377321c34ba5ba51a5f61b5ff", + "blk.2.attn_norm.weight": "6a6743b057e5088f145bc179e92c9bfb41163e7295d7b81c62e23dd89d2b59c4", + "blk.2.attn_output.weight": "bc5491ea54e0db81462d7d9b7d25cbdda380c2db8de041bd1c4ab7b76a1d19c3", + "blk.2.attn_qkv.weight": "a61287a9852e2f5aca9c100b471d98398b2913a3497c743de3c70ec9ddd7087f", + "blk.2.ffn_down.weight": "4fddcc382c8dceeab027fe43d8d44e67edb5e8ce4b9a1b7f773c87770380ade1", + "blk.2.ffn_norm.weight": "07e05f82b3f63f711db3b684ca79aed25c0657917e66f88af47348a82065c227", + "blk.2.ffn_up.weight": "4835a682ef1826c12df01ae7663fc45f9c82bc8e64b665f13fb7da8e201ec0fb", + "blk.3.attn_norm.weight": "f22aba7c03999ba7136f39cda747a39715e498699dc1716cd97fc5dfc58d1b1c", + "blk.3.attn_output.weight": "53b579855366fd786c5126b2b30aac4d583ca7bda56833c4865f5cadb5c18c6d", + "blk.3.attn_qkv.weight": "bb56aba78158123140fcea59c69ac562ca208f6d3086819417cdad8c50f333ad", + "blk.3.ffn_down.weight": "97280897a7cd86db2830c004bccc5bc094f50e293baded0189159a2019145a6e", + "blk.3.ffn_norm.weight": "10a8c99f8b57a960e8e0a1133c4a26f9148403d1b9bff2eff114917de996f3b5", + "blk.3.ffn_up.weight": "7324046c915e75d621b2043597a245a428d8eea31869135e6257a861491d8dcc", + "blk.4.attn_norm.weight": "507d8e164de94646edbfe33def8e8fbf7c9a6ee3fbaedb5000f72d9f51ec5e36", + "blk.4.attn_output.weight": "bbb3429e6efa98c150e0fdbf48c16180cbf0d0cbc1b3c253c6c319d78f4593a2", + "blk.4.attn_qkv.weight": "b95ee5be0786d3901273d806c339fe6c20e6bfffd2a20672a9f56af80921e8ab", + "blk.4.ffn_down.weight": "806bbf91df92a5a22bd5aa1ffb7fc2869f7293ffc7704771c290ecc583b27975", + "blk.4.ffn_norm.weight": "cfc2930a81df7aee3a5e7f726a15c1182233e868bf0d9d37f6b6ae6d8c15c234", + "blk.4.ffn_up.weight": "c3390c69533de2c8424e8069323ccc5d0c4543111535da04cf2c7d26745576aa", + "blk.5.attn_norm.weight": "0d71c4fbcefabbd021569442853d2fe90668b19409ae2805a718a829ca60beab", + "blk.5.attn_output.weight": "10ebd93629112bf2df5c30dd0953a4a5e9020306768283181ed426934d47e14f", + "blk.5.attn_qkv.weight": "5cb05633369f12d4b00e0ff787736bd846856682115720ebc6cce05270c334f6", + "blk.5.ffn_down.weight": "e28bcc5094212eafc7476dbc5b7a520d25b79578cbf4229d698e2655956a80ad", + "blk.5.ffn_norm.weight": "b6f2c4cf9f34bb4d59989f96165c14a67dc1e266ad0a6d0fcc49f1add929e6ff", + "blk.5.ffn_up.weight": "0f9ef99423cc07ebedc0e9cfa95809f2d7108d910bb4ef97ebc0b0309c440750", + "blk.6.attn_norm.weight": "b3edcc47a42218234f7564d7470611b49401a41ae8cd42123f86557c69f5d7f2", + "blk.6.attn_output.weight": "eb9b7d257b388bb5b8fe0515e5c6873317239cb94cda236e4b6ada2a6c57c65c", + "blk.6.attn_qkv.weight": "eb968081f478c52f07bd9c2761741e982dba33cc4eeadeea3557d391b9ac2106", + "blk.6.ffn_down.weight": "1b8588bb7463206290322695577dcfced300895d6e6f4b26966c53a9ae2f0f84", + "blk.6.ffn_norm.weight": "1219c04b7770983c77814200eefe743f46d15328ea2b12711e44f8103eab08d3", + "blk.6.ffn_up.weight": "197ef287239fec47c55677f0fbb66eaf0644f775bc382de843971730721394f6", + "blk.7.attn_norm.weight": "b630ad08c80d564ed1c024384818e9fd3f22a36cd7a14aa96e7e2759a8285099", + "blk.7.attn_output.weight": "970255aa750828a47d6b9d399f9612b5bf25aefe7dadbcba41fc416d0d4067c1", + "blk.7.attn_qkv.weight": "ebb157c880293e6de8d629f263ba8853ed1dbdc02c311d43432bb8cfbb310739", + "blk.7.ffn_down.weight": "24bcd4db4cba844c89f878b81843c373dbbc0675e889d32c5b12e63384a7b670", + "blk.7.ffn_norm.weight": "b9c6f71001808ee873ce7db8056e4b53fb4cccec8b7f0f312899b575fae39d39", + "blk.7.ffn_up.weight": "979f1828d227455c26015a2a11afe9dd05f2bb97a8ba6b38c8dab3f50e627401", + "blk.8.attn_norm.weight": "4e8e347e3775010b7112ee630f2f4f2383be7ff64e6ca6154b9b22566552eaa6", + "blk.8.attn_output.weight": "65a44babf44a435a1829945211b3168f9ec78ac3cb7a049a733e93d11f0d6659", + "blk.8.attn_qkv.weight": "343ed07671da400b040812a4058482fa38284b5d9af9becfed07417fe26ce747", + "blk.8.ffn_down.weight": "7fb7e073e3c2c503c4e9d60efa0988fed7398d900cc003695fe3fffd3e188b82", + "blk.8.ffn_norm.weight": "b07c1f655d8593e3892a2cf73f8a0c19ce8e5cb613fafbe7cbd430da8ce4c57d", + "blk.8.ffn_up.weight": "8b26e14de54b3fdc2e2d3ea41720f9d9c236a93688c3b7fd7bf43f5fbb327c9b", + "blk.9.attn_norm.weight": "46394d408a8e316916177e6aa261de32e137a82d729c0b1800b072f0c38c39b6", + "blk.9.attn_output.weight": "d57f3d46107947a7073373a0b35d6ecf7759b5df15406f4a3590a60666af6b16", + "blk.9.attn_qkv.weight": "14bb8ace8c5453148f4b536e9f4279c813f31136716947256f5cca333448639c", + "blk.9.ffn_down.weight": "2b8d98e2b5ed68338f6e4de43bf7de0c4858cc69103cd5177725f7444eec7694", + "blk.9.ffn_norm.weight": "41a499dfd418cc4c6b8c12313f673f7e2cd4a3f9c4065eb6c4feb5eed02fb542", + "blk.9.ffn_up.weight": "143aab7533a64b17fbe201490a6f674bc7f0bd370c094500b2e100419073d1c2", + "blk.10.attn_norm.weight": "ebb670aafd36816a794347287269d8f1a5b19c1e3c0a1e38023bc19fdba9b073", + "blk.10.attn_output.weight": "b5d65bbc0ed5e49fdd9d754bc18163cd042a285024d0cf6f954c503bc8c877cb", + "blk.10.attn_qkv.weight": "f06b15bac88da798fa34a62b03eaac0dbe8b846020516603c387541f2d8dd672", + "blk.10.ffn_down.weight": "fb091fcd1b4de25d1bea94d1755e255cb02914a030d23e3a234e57b8d46bde6e", + "blk.10.ffn_norm.weight": "eb347bdf9c40414af87e13a8e72e40b31f004b50f7cb366f1a219ced60a61355", + "blk.10.ffn_up.weight": "ed2d52fc881a173f404fe8a1067862c9856d6c3e0d2e90a330a7aa394e3f84d1", + "blk.11.attn_norm.weight": "64e252603cf010a0e502ca39fdf8d0a196a79aec67c0d2bb9213fc0cb80c47d4", + "blk.11.attn_output.weight": "228e33e21c69f52efc74fdfc831bc9af271e44b2a29a3dced1d64e667ce36eb5", + "blk.11.attn_qkv.weight": "ab9ce6d4ef9e42ee0da3f20a7708a3bbc5e79e967b05fa86ba946a05e2eb63eb", + "blk.11.ffn_down.weight": "0ca133b7835c98dc77c25d64e4eb7873778bdb5e4d22d8b80f920f46865b43bd", + "blk.11.ffn_norm.weight": "02455741a0dfd161c79aa1ecc381901721f229fdcda5615622a629631fb61cfd", + "blk.11.ffn_up.weight": "9fecdcc099fbb8e23c6b1ea9294702a027f4a58d265543ec5e7be79b8f63b354", + "blk.12.attn_norm.weight": "783bb459911b1b3609a9b2bdfe272f1670add73b5471da738e07ac47e2e07dfd", + "blk.12.attn_output.weight": "1e1a914c9e48b857206ac5a1f7cead994bc1ea91d5d4fff8c834d73f2e38ef5d", + "blk.12.attn_qkv.weight": "5953e7185ccb87fb4dae8f9426ec86315d4c7794326e8ab59b3a95d4af2189f0", + "blk.12.ffn_down.weight": "a3eecf0f394f86e2cfb48a5940a5c50ca86d71883b2f79fcc642a935fabce0d4", + "blk.12.ffn_norm.weight": "0a4272e41373c23bd72f10d2d82930aa3a1480aac75832bfbf01cebf0b86b6a4", + "blk.12.ffn_up.weight": "06f42776de3a7ceac3025f26a7a8bd20e062233cce2bdaa2183470dc4b30b87d", + "blk.13.attn_norm.weight": "5915da60fb03e201fa649faba780e5fdf1c761c262b206e5415cf83181f65780", + "blk.13.attn_output.weight": "4dbf6eab074fa3835fd32bd631a8208e511037d5056d2fd3015735cca7674ef7", + "blk.13.attn_qkv.weight": "d3d8339a1c4782d9e73d77fdebe154d3c5b83ac40c9175b3e91a4977d08f876b", + "blk.13.ffn_down.weight": "de6772b46a55e1fd42b007637dfbf68b6598e5d5b61622da0935002e1e192d3a", + "blk.13.ffn_norm.weight": "5a640ea3b8c7be49c95a58a2327e10d8e8d9d142504bde5c8091613e5b961d7a", + "blk.13.ffn_up.weight": "f35e3545e4bd3531b2e843b5efd31dee0c13c807ee6386e65473ba67bbec30d0", + "blk.14.attn_norm.weight": "9b34986450b7c98b4927e81e61a816f9e84b1addc7c14926402100037aad6678", + "blk.14.attn_output.weight": "155d52efb23d366016d861a251d4d1f4a0c13699188c50d50dba016a0d8bfcd9", + "blk.14.attn_qkv.weight": "8e1415084e1f33c73a777f19e752489f4dd312cca047733e5ea643cd4a955e04", + "blk.14.ffn_down.weight": "a2a142226b94baa01ccb65bdea2b7418e49085c1d9c3c63e544e3112c58a25da", + "blk.14.ffn_norm.weight": "8aecfd9b0ae6affaea31a80c5c9a4a14b31deaa0db7bd8f6da2a64d23447921c", + "blk.14.ffn_up.weight": "0c1407237b8c1bd02f193346b5681926fe698a5055eac6a7450451b0f991707c", + "blk.15.attn_norm.weight": "e037bd19880bfa83d983200fb0c7866f8ad16c3ff5cc4b4f3a37ca7373870ff6", + "blk.15.attn_output.weight": "045fe4fc95cc129a1b92771b179c11b12845c4c088786c607f17bd98857e68e1", + "blk.15.attn_qkv.weight": "7621b7559705cab1d4dea1c69f76dbf9dc1c8837a203b656f484703b9c1b70ce", + "blk.15.ffn_down.weight": "7e5ac20e290bc60761e1cd972354fde225b7fa861048d44d9a0dd9b046d55f58", + "blk.15.ffn_norm.weight": "b6d830d88f1db1825687973c8c2b1a24c6fa84f07af8d0e3ef9c86009baca0b2", + "blk.15.ffn_up.weight": "dcda0957cd04fc45476774dba2bbf9aa89d6b05d5ca7b10ae6f73ad2c49b1cd3", + "blk.16.attn_norm.weight": "4ee9b70ba15cb2a08240f93990e90f5068c48fceb481f8e2186bec8b7214eb3f", + "blk.16.attn_output.weight": "315cfe5536658d2498192b2980eade15b2c9a4ff220e4011911457b1727fa103", + "blk.16.attn_qkv.weight": "3c8122e3ad637583b9dcde8ff3a323267d3014bb1f0f9771e5322260ca9ecc8d", + "blk.16.ffn_down.weight": "3b5fbebd5ee2b86cad96fb8a9b45a8770d08f82c1c8b74d7061e866f7020a18d", + "blk.16.ffn_norm.weight": "ffab69f20bda372de6e5878f0539163e2fc6ba113621ded95705fc3b1465c9f0", + "blk.16.ffn_up.weight": "0935ea3d258da42d6258406365f39f58ddaabfe97ea5977580db3635188f24a1", + "blk.17.attn_norm.weight": "f030441733f3d147b4a06a1eb4aeb8465c7c24d9c53bf4c48fe7e134d3629803", + "blk.17.attn_output.weight": "07a955ef09e8dc766ac0df647d0b2c69f23c4c69a7137654b4aad80303ed0eda", + "blk.17.attn_qkv.weight": "1c10688061e21e2fe12ad0cb54bf03895c1f83c3b0df743a42f548b52cbca1b2", + "blk.17.ffn_down.weight": "ebb9cc9836f41d88fdae2aa9a4355514e4edaec8d1577ffeb947a35204e77f52", + "blk.17.ffn_norm.weight": "50aff44f6528b13db5389f2ddcdb7676244947610bd7ffbff3f881c968c2a0d4", + "blk.17.ffn_up.weight": "d716537949582be33bde6b02e38f5a70081c9642a9fb05a61312126718b8d148", + "blk.18.attn_norm.weight": "0ea695c4e53d637902f46663a6ee42adc493c36794476acc7dbddaa05b13840d", + "blk.18.attn_output.weight": "5fd35b500221a612eb4f4bddf0e9b6b7db4d7733032a75f8802fb2d884647c2e", + "blk.18.attn_qkv.weight": "b0da37fd030fe69581f990bf23bfd35467a1bbe558af6de7c0924f6b72e92317", + "blk.18.ffn_down.weight": "b355c33f44b328f4bb977567de8f7544db4b005d7a8fbded658518ecf3c5a153", + "blk.18.ffn_norm.weight": "58b3fe9094079989a86e0387143259e1cc35952d24dc3df290c4ba6df44f5c51", + "blk.18.ffn_up.weight": "2ce530954c342c30ed2ead5353f931960bfae1d278868504c0efb973560fabbe", + "blk.19.attn_norm.weight": "533e9aed66feea8f0392aa81f9e293240e1f009a5334253915fb60c2749b615d", + "blk.19.attn_output.weight": "84f2d00f98a4113a779d3b5d1c3e7c914eb47784d3ab13b290367c124c2994aa", + "blk.19.attn_qkv.weight": "fbe6b9f53b07fa7537d3b3d452d20a9bc666f9fd41ec2091dd28bc2f70fc668f", + "blk.19.ffn_down.weight": "b30199e098c8bb3f890183d8b18471e80b62b604729b277ad62488dd71e1206b", + "blk.19.ffn_norm.weight": "c81373e41cd340b7badb19f9517c77c4250b4eb9a02dc758b8b49b652487d7ff", + "blk.19.ffn_up.weight": "5a5cb083ca7725720e3a890f7fa46354760e8007a8188849a092e305694a75e3", + "blk.20.attn_norm.weight": "4953091b4477e354357a8e743ba0a1900633e52f1599ee082a0c9b0b2b5cd978", + "blk.20.attn_output.weight": "62d54f7749cd6856097b2632066a322b0296df915fe66f382c5b5981be0d4f23", + "blk.20.attn_qkv.weight": "406de9e35b0729ebe902d7a47905cc7fb29a921431ed35dbef0c03e5690a1329", + "blk.20.ffn_down.weight": "62fb678b0d1261e19a4903a2b347d67afcc8acff01feb33a687a35a2d1e6f9a5", + "blk.20.ffn_norm.weight": "cd9d36b7e71e55c8925b97bb09c28219f182626bcff094878ae39c3db887a14b", + "blk.20.ffn_up.weight": "b9276771d79d3e932e73ccc520c3f8476342b9ef312ed2ee1e0da822e6e3ad18", + "blk.21.attn_norm.weight": "66d8c8a35e13ce9c2a0e75b670150e2c31484a55c2316df46075312196178ed3", + "blk.21.attn_output.weight": "12ab46c9382648f9b3350fdd92a6be6352743d62d6b520d7e2024e0c838588f5", + "blk.21.attn_qkv.weight": "a7909676ee1675ca23cd29a5fdd226df8dd9d68f94c6c9bbb51dd9fd38504008", + "blk.21.ffn_down.weight": "6fb317279c6542e82f97d5a12a60fac1bd0fa0405154f9fbe265e2fe39bd49cc", + "blk.21.ffn_norm.weight": "c0f703eb3ff161b5ba4490d87d8684b8a6c47a8f433e12f418333b9db439010a", + "blk.21.ffn_up.weight": "6dbdb80ef0c35e364bbce12d40d5e74c7963c7b55d58d9579567a07ffce7b863", + "blk.22.attn_norm.weight": "f94237433bf03d675cb2f655b81ca91a1ce2447bc6b00b13d6b0ccfe2d411eff", + "blk.22.attn_output.weight": "e821f95995ce497c01e63ca64f737713b1b65f11df1903e51d444aa516f33f71", + "blk.22.attn_qkv.weight": "1b0f717c73afb5eb4c82a1708c4e85c969e8a2a8770d9ddb78b1870a2d8a781e", + "blk.22.ffn_down.weight": "0f33f7a3cdc685484be99aa0c03642b0b20850a27d1fddbe054b13a9382f3ccb", + "blk.22.ffn_norm.weight": "9df285cf211ddd7df2b36a50489af574755c7d4d98b29a05cd04566ae613c8dc", + "blk.22.ffn_up.weight": "63ac300e1efb34041dd0136cf43ea622fac6f0caccce1cd9262f5e08d2cf179c", + "blk.23.attn_norm.weight": "5f72d9e88689b4027b28f5f8f26cd3abb03635ceea7ec98a4c91a9fc691f6707", + "blk.23.attn_output.weight": "6ecf04ff61125c5fc768f8656497152149373daf321ee9c957e8f7245a1184d1", + "blk.23.attn_qkv.weight": "a9d9978806724c2959f2cf386c233831f08e1e933dbf2b32665e788d9d512ea4", + "blk.23.ffn_down.weight": "72c7d17886a3da17fa0daa456aa5e877b2ef5b8b403182b870d9ca5ca9c70347", + "blk.23.ffn_norm.weight": "971e4b712e3025a13419b5b57d674b5e4ab7f18f74b57b9afc4671623da90c4b", + "blk.23.ffn_up.weight": "df2b5c7dbd5834545b815073af0c7355b065124e6d6f0fee78d8fa5b2076dc3e", + "blk.24.attn_norm.weight": "c41957c4a79ad3b16f6e11daec1c7f530b9f3f4b618e1e4367c3b67787ac4ab6", + "blk.24.attn_output.weight": "ef7d61f5fc88ac6f31bf60cb5f4d2d6b8df42d38825807112361a7224b0dee3b", + "blk.24.attn_qkv.weight": "3e6a58fe7d49c90bb6971efbad3371c32256881173ea5aee4b0c296cb206490f", + "blk.24.ffn_down.weight": "f43619144047de42fed81dfa495f1815d3cb771330e574043e2b67620819292c", + "blk.24.ffn_norm.weight": "5501d4a2a98c8ca6b42e77b53b221dbc08f530f6a067256d787534ec6fe028bd", + "blk.24.ffn_up.weight": "d64c8b0e509e2b1118f6000176f8956cacecdbb200c7e95ed93fb78b6e26c84a", + "blk.25.attn_norm.weight": "502fa3c302d371f61c5791f4615b73018ffb1daa09b6499b227116581244c5d4", + "blk.25.attn_output.weight": "ad8391d4e9c980856f2547aa945b2b6a407a6382158dc1ddd4f08d94ecc24be6", + "blk.25.attn_qkv.weight": "42e8983780d4a01a02c54ad23d4df21eea437f119a10af5a9c12a76a42d308c1", + "blk.25.ffn_down.weight": "302dd010d4e0ab4eeaee89090409ea0dddeeeed3236415eb8f97c942497eea91", + "blk.25.ffn_norm.weight": "fb34c1ee5bca96986c08834df0a0c047ba041c1123ac1f563e9d64312bf82d6a", + "blk.25.ffn_up.weight": "10739a8de156816d93c92b935386540bfa976bdbef204f0312960f6fc657582f", + "blk.26.attn_norm.weight": "7036c711609128c4e55968ff3681d3043338879a5737efd6c2ac9e1a2a61f1a0", + "blk.26.attn_output.weight": "db5db45dead5cb911fa01da59832f121b7c18b2d167bf53741c40819f24d346c", + "blk.26.attn_qkv.weight": "cae34c6b7f82ed14348d5ed30a79919c383737c1694a9cb9c0de609d3b0c1d0a", + "blk.26.ffn_down.weight": "491ec3a4da9b4f49f8ebc6be658ce397a9b801ae9fb35e82177e47808c65e5d0", + "blk.26.ffn_norm.weight": "fd7059d75d7f0e5288511ddeeb0f772eb3cae3ccfe4226b877015834edc3c386", + "blk.26.ffn_up.weight": "ea1ee1274c56458ce056d2205e5bb6e5422ce4cb0ad58006b8141749b97a0c39", + "blk.27.attn_norm.weight": "cc362c9a937609265052cd38544af17a1a7448cea086d4c801139e1fc865832d", + "blk.27.attn_output.weight": "ba757a81dabde9cb1b069d1bb616fe79649a1724f756567ec61caed1304fe6cf", + "blk.27.attn_qkv.weight": "1ab8d7d02d87756c12c2275636823aa5ede3d683178225c4cac4bd892c319bd4", + "blk.27.ffn_down.weight": "deb1c711c8a66acf4dcd2d088e1548f8e08f296f755e4067d6557fa55afde88c", + "blk.27.ffn_norm.weight": "fc6242d8cb8a4a37a8ddb7e41e7e60a63d4a89edf36acb35df052f10b9c91ece", + "blk.27.ffn_up.weight": "8df39b09c4801f343aca78f2918a1f6db78c8c55e591eda4c69eadb74c26e180", + "blk.28.attn_norm.weight": "75b539308f77e3cefdc6d98484d8b5cbf0538f0c2869a77b7373a145a18bc850", + "blk.28.attn_output.weight": "ae128940eb60a6d2e121762ef4b3e9dcf9eb3e105b249507fa7f12de0e19822c", + "blk.28.attn_qkv.weight": "bdda781c288e9326c240e33905f8e621b6a2ad902e620739d34f93fcd6f933de", + "blk.28.ffn_down.weight": "f1d6e6d1c286b1138bfd7e53fe477f399ae93bc2c04e35416f84218ed7247965", + "blk.28.ffn_norm.weight": "3f837ce82c8b9bde0d61d08b6f5fe5574886ea5328dbdc53f2929f18da8b4087", + "blk.28.ffn_up.weight": "2af027002e31d1b6cfedbdb30a2b9d7213f3aa691167c353913adfd48fda31e4", + "blk.29.attn_norm.weight": "61e8003b5329462ffe0fe172f2b160260de006aed858332d49d75504b6b6aa7a", + "blk.29.attn_output.weight": "ca44542a72a37476dc73dbdcc01f5b7497cb3ebc4ea230a55c9634ccd8e56ad4", + "blk.29.attn_qkv.weight": "abb3d9d6abe57872ae3daa51935d43264093ded5ce63b49d1e280ee5758be0e4", + "blk.29.ffn_down.weight": "6764b895fce881df097489c263446f0106de36217997660c15984b3ee22a5a06", + "blk.29.ffn_norm.weight": "89e03e9a33fc0e6e31ba9f0c2bd7c5734a118c5602bb90148793e08a80e8d0ae", + "blk.29.ffn_up.weight": "fa7ad57a84954f4121653152efed1a871d8adb20a1ea9086e3e849ce359d7d2e", + "blk.30.attn_norm.weight": "91a697aca1e42af54f806a20211031c3369e8d0bd58df1b0147fe24954e1f5a4", + "blk.30.attn_output.weight": "36063fcf766c89ac75be56f688cc63cefe5f2c733fbf4378ea9956ad386fa148", + "blk.30.attn_qkv.weight": "2cacd1161f1121a2c0b979930134f4666f73fb8d7237b3b0659ae091b15955a6", + "blk.30.ffn_down.weight": "9f3fcb6217100595850c05dc98f9ab2a263afdb6ab28df2fcb08aeff512057d7", + "blk.30.ffn_norm.weight": "6c600bc1fc7de39d4f8917b81fc7d1d5ed2a9b56492234c13a4bd6028c30d880", + "blk.30.ffn_up.weight": "73cabd1bb011956b2689ea3338bb76642ef3a57c197377d666d2ab5f56317668", + "blk.31.attn_norm.weight": "72d3e1cc771380645fa75a899858c95f39857a4f3f1ed60fe1578df383b8bc53", + "blk.31.attn_output.weight": "40089cdd29994dc19a1d89fa15902a89cfeca3540f12dc9bf4d00ef82506e456", + "blk.31.attn_qkv.weight": "1d0bb40e9258071ae14290a53c619a8e331dda07354d2a02ef45766c029ae5e4", + "blk.31.ffn_down.weight": "8defa0e06335b793fa8be03883f0a322d6c5b33f52c69c943c35c60d16e42c0a", + "blk.31.ffn_norm.weight": "33c55d9d0c496ccfb130361fe131649346e098abaaac39c0519507e5d846721d", + "blk.31.ffn_up.weight": "599f6503f61c692c1f82001973d35119f9688db5e6be9d9c298411491c93f09b", + "output.weight": "14b8dc662bfa3308ebb2e102c562d8e52c15670e538f20f3216a9c310ca9dd41", + "output_norm.weight": "7f2294ba94ce65681df6c7ddd8698799199b9d77dc83c10bdad5c3999f0fdb82", + "rope_factors_long.weight": "e34d378664e354652c38f47d10dafb0498ccc2fb042d39ff7fef768146fff22b", + "rope_factors_short.weight": "9379146a4988f373d362fe47b06c75e7fe7c54aa4dc9558758df79b7a87471fd", + "token_embd.weight": "19a03c1fb5ac0baee93b0a7d8b0f26e9a9b011e229b694afc50ebfc13d84f8bf" +} diff --git a/llm/ggml.go b/llm/ggml.go index d7f2eef7c..4c68adf97 100644 --- a/llm/ggml.go +++ b/llm/ggml.go @@ -157,6 +157,14 @@ type Tensor struct { io.WriterTo `json:"-"` } +func (t Tensor) block() (n int) { + if _, err := fmt.Sscanf(t.Name, "blk.%d.", &n); err != nil { + return -1 + } + + return +} + func (t Tensor) blockSize() uint64 { switch t.Kind { case 0, 1, 24, 25, 26, 27, 28, 30: // F32, F16, I8, I16, I32, I64, F64, BF16 diff --git a/llm/gguf.go b/llm/gguf.go index 981583131..2e6bc542a 100644 --- a/llm/gguf.go +++ b/llm/gguf.go @@ -532,15 +532,14 @@ func WriteGGUF(ws io.WriteSeeker, kv KV, ts []Tensor) error { } } - slices.SortFunc(ts, func(a, b Tensor) int { - var i, j int - if n, err := fmt.Sscanf(a.Name, "blk.%d", &i); err != nil || n != 1 { - return cmp.Compare(a.Name, b.Name) - } else if n, err := fmt.Sscanf(b.Name, "blk.%d", &j); err != nil || n != 1 { - return cmp.Compare(a.Name, b.Name) + slices.SortStableFunc(ts, func(a, b Tensor) int { + if i, j := a.block(), b.block(); i < 0 && j > 0 { + return 1 + } else if i > 0 && j < 0 { + return -1 + } else { + return cmp.Compare(i, j) } - - return cmp.Compare(i, j) }) var s uint64 From aec77d6a05c3cd13732eab7decc9794bbed670d9 Mon Sep 17 00:00:00 2001 From: Bruce MacDonald Date: Tue, 2 Jul 2024 14:40:01 -0700 Subject: [PATCH 238/384] support new "longrope" attention factor --- convert/convert_phi3.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/convert/convert_phi3.go b/convert/convert_phi3.go index 7aa3ed150..0f645217d 100644 --- a/convert/convert_phi3.go +++ b/convert/convert_phi3.go @@ -58,7 +58,7 @@ func (p *phi3) KV(t *Tokenizer) llm.KV { switch p.RopeScaling.Type { case "": // no scaling - case "su": + case "su", "longrope": kv["phi3.rope.scaling.attn_factor"] = float32(max(math.Sqrt(1+math.Log(scale)/math.Log(float64(p.OriginalMaxPositionEmbeddings))), 1.0)) case "yarn": kv["phi3.rope.scaling.attn_factor"] = float32(max(0.1*math.Log(scale)+1.0, 1.0)) From bd5e432630a0c1d1ca5795052355a45014e71a2a Mon Sep 17 00:00:00 2001 From: Michael Yang Date: Mon, 5 Aug 2024 10:30:32 -0700 Subject: [PATCH 239/384] update import.md --- docs/import.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/import.md b/docs/import.md index f34f09ace..82ea9ba5d 100644 --- a/docs/import.md +++ b/docs/import.md @@ -16,7 +16,9 @@ If the model being imported is one of these architectures, it can be imported di - LlamaForCausalLM - MistralForCausalLM + - MixtralForCausalLM - GemmaForCausalLM + - Phi3ForCausalLM ```dockerfile FROM /path/to/safetensors/directory From 8b00a415ab5170a5a75b105402ca262d1fb7ac12 Mon Sep 17 00:00:00 2001 From: royjhan <65097070+royjhan@users.noreply.github.com> Date: Tue, 13 Aug 2024 13:19:56 -0400 Subject: [PATCH 240/384] Load Embedding Model on Empty Input (#6325) * load on empty input * no load on invalid input --- server/routes.go | 16 +++++----- server/routes_test.go | 70 ------------------------------------------- 2 files changed, 9 insertions(+), 77 deletions(-) diff --git a/server/routes.go b/server/routes.go index e5a31002f..6c470c174 100644 --- a/server/routes.go +++ b/server/routes.go @@ -324,13 +324,10 @@ func (s *Server) EmbedHandler(c *gin.Context) { input = append(input, v.(string)) } default: - c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "invalid input type"}) - return - } - - if len(input) == 0 { - c.JSON(http.StatusOK, api.EmbedResponse{Model: req.Model, Embeddings: [][]float32{}}) - return + if req.Input != nil { + c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "invalid input type"}) + return + } } r, m, opts, err := s.scheduleRunner(c.Request.Context(), req.Model, []Capability{}, req.Options, req.KeepAlive) @@ -341,6 +338,11 @@ func (s *Server) EmbedHandler(c *gin.Context) { checkpointLoaded := time.Now() + if len(input) == 0 { + c.JSON(http.StatusOK, api.EmbedResponse{Model: req.Model, Embeddings: [][]float32{}}) + return + } + kvData, err := getKVData(m.ModelPath, false) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) diff --git a/server/routes_test.go b/server/routes_test.go index ef7248ef7..242875d6c 100644 --- a/server/routes_test.go +++ b/server/routes_test.go @@ -272,76 +272,6 @@ func Test_Routes(t *testing.T) { assert.Equal(t, "library", retrieveResp.OwnedBy) }, }, - { - Name: "Embed Handler Empty Input", - Method: http.MethodPost, - Path: "/api/embed", - Setup: func(t *testing.T, req *http.Request) { - embedReq := api.EmbedRequest{ - Model: "t-bone", - Input: "", - } - jsonData, err := json.Marshal(embedReq) - require.NoError(t, err) - req.Body = io.NopCloser(bytes.NewReader(jsonData)) - }, - Expected: func(t *testing.T, resp *http.Response) { - contentType := resp.Header.Get("Content-Type") - if contentType != "application/json; charset=utf-8" { - t.Fatalf("expected content type application/json; charset=utf-8, got %s", contentType) - } - body, err := io.ReadAll(resp.Body) - if err != nil { - t.Fatal(err) - } - - var embedResp api.EmbedResponse - err = json.Unmarshal(body, &embedResp) - if err != nil { - t.Fatal(err) - } - - if embedResp.Model != "t-bone" { - t.Fatalf("expected model t-bone, got %s", embedResp.Model) - } - - if embedResp.Embeddings == nil { - t.Fatalf("expected embeddings to not be nil, got %v", embedResp.Embeddings) - } - - if len(embedResp.Embeddings) != 0 { - t.Fatalf("expected embeddings to be empty, got %v", embedResp.Embeddings) - } - }, - }, - { - Name: "Embed Handler Invalid Input", - Method: http.MethodPost, - Path: "/api/embed", - Setup: func(t *testing.T, req *http.Request) { - embedReq := api.EmbedRequest{ - Model: "t-bone", - Input: 2, - } - jsonData, err := json.Marshal(embedReq) - require.NoError(t, err) - req.Body = io.NopCloser(bytes.NewReader(jsonData)) - }, - Expected: func(t *testing.T, resp *http.Response) { - contentType := resp.Header.Get("Content-Type") - if contentType != "application/json; charset=utf-8" { - t.Fatalf("expected content type application/json; charset=utf-8, got %s", contentType) - } - _, err := io.ReadAll(resp.Body) - if err != nil { - t.Fatal(err) - } - - if resp.StatusCode != http.StatusBadRequest { - t.Fatalf("expected status code 400, got %d", resp.StatusCode) - } - }, - }, } t.Setenv("OLLAMA_MODELS", t.TempDir()) From feedf49c717a449cedbf973b06ca97796cfaa004 Mon Sep 17 00:00:00 2001 From: Daniel Hiltgen Date: Tue, 13 Aug 2024 11:44:50 -0700 Subject: [PATCH 241/384] Go back to a pinned Go version Go version 1.22.6 is triggering AV false positives, so go back to 1.22.5 --- .github/workflows/release.yaml | 10 +++++----- .github/workflows/test.yaml | 10 +++++----- go.mod | 2 +- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index f0c6db5dd..5ae630c31 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -31,7 +31,7 @@ jobs: security set-keychain-settings -lut 3600 build.keychain - uses: actions/setup-go@v5 with: - go-version: "stable" + go-version-file: go.mod cache: true - name: Build Darwin env: @@ -87,7 +87,7 @@ jobs: write-host "plugin installed" - uses: actions/setup-go@v5 with: - go-version: "stable" + go-version-file: go.mod cache: true - run: go get ./... - run: | @@ -141,7 +141,7 @@ jobs: write-host "plugin installed" - uses: actions/setup-go@v5 with: - go-version: "stable" + go-version-file: go.mod cache: true - name: 'Install ROCm' run: | @@ -218,7 +218,7 @@ jobs: write-host "plugin installed" - uses: actions/setup-go@v5 with: - go-version: "stable" + go-version-file: go.mod cache: true - name: 'Install CUDA' run: | @@ -306,7 +306,7 @@ jobs: write-host "plugin installed" - uses: actions/setup-go@v5 with: - go-version: "stable" + go-version-file: go.mod cache: true - run: go get - uses: actions/download-artifact@v4 diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index a57d45fd7..3d58fa3e2 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -63,7 +63,7 @@ jobs: - uses: actions/checkout@v4 - uses: actions/setup-go@v5 with: - go-version: "stable" + go-version-file: go.mod cache: true - run: go get ./... - run: | @@ -163,7 +163,7 @@ jobs: - uses: actions/checkout@v4 - uses: actions/setup-go@v5 with: - go-version: "stable" + go-version-file: go.mod cache: true - name: 'Install ROCm' run: | @@ -200,7 +200,7 @@ jobs: - uses: actions/checkout@v4 - uses: actions/setup-go@v5 with: - go-version: "stable" + go-version-file: go.mod cache: true - name: 'Install CUDA' run: | @@ -255,7 +255,7 @@ jobs: submodules: recursive - uses: actions/setup-go@v5 with: - go-version: "stable" + go-version-file: go.mod cache: false - run: | case ${{ matrix.arch }} in @@ -297,7 +297,7 @@ jobs: submodules: recursive - uses: actions/setup-go@v5 with: - go-version: "stable" + go-version-file: go.mod cache: true - run: | case ${{ matrix.arch }} in diff --git a/go.mod b/go.mod index 2e0c6614c..6e437c730 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/ollama/ollama -go 1.22.0 +go 1.22.5 require ( github.com/containerd/console v1.0.3 From 1f32276178d5860bbaeafb7dd73d4ef93053bc15 Mon Sep 17 00:00:00 2001 From: Pamela Fox Date: Tue, 13 Aug 2024 13:36:05 -0700 Subject: [PATCH 242/384] Update openai.md to remove extra checkbox (#6345) --- docs/openai.md | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/openai.md b/docs/openai.md index 7b3a3f315..75d2c5955 100644 --- a/docs/openai.md +++ b/docs/openai.md @@ -182,7 +182,6 @@ curl http://localhost:11434/v1/embeddings \ - [x] Reproducible outputs - [x] Vision - [x] Tools (streaming support coming soon) -- [ ] Vision - [ ] Logprobs #### Supported request fields From 2697d7f5aad27248aebbe5acff1dcbede5367b7b Mon Sep 17 00:00:00 2001 From: Michael Yang Date: Tue, 13 Aug 2024 13:40:37 -0700 Subject: [PATCH 243/384] lint - fixes printf: non-constant format string in call to fmt.Printf - fixes SA1032: arguments have the wrong order - disables testifylint --- .golangci.yaml | 1 - readline/buffer.go | 79 ++++++++++++++++---------------------------- readline/readline.go | 2 +- readline/types.go | 61 ++++++++++++++++++++++------------ server/sched.go | 2 +- 5 files changed, 70 insertions(+), 75 deletions(-) diff --git a/.golangci.yaml b/.golangci.yaml index c2c8b52b2..c9c9f620b 100644 --- a/.golangci.yaml +++ b/.golangci.yaml @@ -24,7 +24,6 @@ linters: - nosprintfhostport - staticcheck - tenv - - testifylint - unconvert - unused - usestdlibvars diff --git a/readline/buffer.go b/readline/buffer.go index 68573d400..d91fe0a99 100644 --- a/readline/buffer.go +++ b/readline/buffer.go @@ -62,7 +62,7 @@ func (b *Buffer) MoveLeft() { rLength := runewidth.RuneWidth(r) if b.DisplayPos%b.LineWidth == 0 { - fmt.Printf(CursorUp + CursorBOL + cursorRightN(b.Width)) + fmt.Print(CursorUp + CursorBOL + CursorRightN(b.Width)) if rLength == 2 { fmt.Print(CursorLeft) } @@ -74,7 +74,7 @@ func (b *Buffer) MoveLeft() { fmt.Print(CursorLeft) } } else { - fmt.Print(cursorLeftN(rLength)) + fmt.Print(CursorLeftN(rLength)) } b.Pos -= 1 @@ -115,15 +115,15 @@ func (b *Buffer) MoveRight() { b.DisplayPos += rLength if b.DisplayPos%b.LineWidth == 0 { - fmt.Printf(CursorDown + CursorBOL + cursorRightN(len(b.Prompt.prompt()))) + fmt.Print(CursorDown + CursorBOL + CursorRightN(len(b.Prompt.prompt()))) } else if (b.DisplayPos-rLength)%b.LineWidth == b.LineWidth-1 && hasSpace { - fmt.Printf(CursorDown + CursorBOL + cursorRightN(len(b.Prompt.prompt())+rLength)) + fmt.Print(CursorDown + CursorBOL + CursorRightN(len(b.Prompt.prompt())+rLength)) b.DisplayPos += 1 } else if b.LineHasSpace.Size() > 0 && b.DisplayPos%b.LineWidth == b.LineWidth-1 && hasSpace { - fmt.Printf(CursorDown + CursorBOL + cursorRightN(len(b.Prompt.prompt()))) + fmt.Print(CursorDown + CursorBOL + CursorRightN(len(b.Prompt.prompt()))) b.DisplayPos += 1 } else { - fmt.Print(cursorRightN(rLength)) + fmt.Print(CursorRightN(rLength)) } } } @@ -154,7 +154,7 @@ func (b *Buffer) MoveToStart() { fmt.Print(CursorUp) } } - fmt.Printf(CursorBOL + cursorRightN(len(b.Prompt.prompt()))) + fmt.Print(CursorBOL + CursorRightN(len(b.Prompt.prompt()))) b.Pos = 0 b.DisplayPos = 0 } @@ -169,9 +169,9 @@ func (b *Buffer) MoveToEnd() { fmt.Print(CursorDown) } remainder := b.DisplaySize() % b.LineWidth - fmt.Printf(CursorBOL + cursorRightN(len(b.Prompt.prompt())+remainder)) + fmt.Print(CursorBOL + CursorRightN(len(b.Prompt.prompt())+remainder)) } else { - fmt.Print(cursorRightN(b.DisplaySize() - b.DisplayPos)) + fmt.Print(CursorRightN(b.DisplaySize() - b.DisplayPos)) } b.Pos = b.Buf.Size() @@ -286,8 +286,7 @@ func (b *Buffer) drawRemaining() { remLength := runewidth.StringWidth(remainingText) if len(currLine) > 0 { - fmt.Printf(ClearToEOL + currLine) - fmt.Print(cursorLeftN(currLineSpace)) + fmt.Print(ClearToEOL + currLine + CursorLeftN(currLineSpace)) } else { fmt.Print(ClearToEOL) } @@ -301,9 +300,9 @@ func (b *Buffer) drawRemaining() { } if (b.DisplayPos+currLineSpace)%b.LineWidth == 0 && currLine == remainingText { - fmt.Print(cursorRightN(currLineSpace)) + fmt.Print(CursorRightN(currLineSpace)) fmt.Printf("\n%s", b.Prompt.AltPrompt) - fmt.Printf(CursorUp + CursorBOL + cursorRightN(b.Width-currLineSpace)) + fmt.Print(CursorUp + CursorBOL + CursorRightN(b.Width-currLineSpace)) } // render the other lines @@ -333,9 +332,7 @@ func (b *Buffer) drawRemaining() { lineLength += runewidth.RuneWidth(c) fmt.Printf("%c", c) } - fmt.Print(ClearToEOL) - fmt.Print(cursorUpN(totalLines)) - fmt.Printf(CursorBOL + cursorRightN(b.Width-currLineSpace)) + fmt.Print(ClearToEOL + CursorUpN(totalLines) + CursorBOL + CursorRightN(b.Width-currLineSpace)) hasSpace := b.GetLineSpacing(b.DisplayPos / b.LineWidth) @@ -357,8 +354,7 @@ func (b *Buffer) Remove() { if b.DisplayPos%b.LineWidth == 0 { // if the user backspaces over the word boundary, do this magic to clear the line // and move to the end of the previous line - fmt.Printf(CursorBOL + ClearToEOL) - fmt.Printf(CursorUp + CursorBOL + cursorRightN(b.Width)) + fmt.Print(CursorBOL + ClearToEOL + CursorUp + CursorBOL + CursorRightN(b.Width)) if b.DisplaySize()%b.LineWidth < (b.DisplaySize()-rLength)%b.LineWidth { b.LineHasSpace.Remove(b.DisplayPos/b.LineWidth - 1) @@ -370,24 +366,23 @@ func (b *Buffer) Remove() { } if rLength == 2 { - fmt.Print(CursorLeft + " " + cursorLeftN(2)) + fmt.Print(CursorLeft + " " + CursorLeftN(2)) } else { fmt.Print(" " + CursorLeft) } } else if (b.DisplayPos-rLength)%b.LineWidth == 0 && hasSpace { - fmt.Printf(CursorBOL + ClearToEOL) - fmt.Printf(CursorUp + CursorBOL + cursorRightN(b.Width)) + fmt.Print(CursorBOL + ClearToEOL + CursorUp + CursorBOL + CursorRightN(b.Width)) if b.Pos == b.Buf.Size() { b.LineHasSpace.Remove(b.DisplayPos/b.LineWidth - 1) } b.DisplayPos -= 1 } else { - fmt.Print(cursorLeftN(rLength)) + fmt.Print(CursorLeftN(rLength)) for range rLength { fmt.Print(" ") } - fmt.Print(cursorLeftN(rLength)) + fmt.Print(CursorLeftN(rLength)) } var eraseExtraLine bool @@ -405,9 +400,9 @@ func (b *Buffer) Remove() { // are trailing characters which go over the line width boundary if eraseExtraLine { remainingLines := (b.DisplaySize() - b.DisplayPos) / b.LineWidth - fmt.Printf(cursorDownN(remainingLines+1) + CursorBOL + ClearToEOL) + fmt.Print(CursorDownN(remainingLines+1) + CursorBOL + ClearToEOL) place := b.DisplayPos % b.LineWidth - fmt.Printf(cursorUpN(remainingLines+1) + cursorRightN(place+len(b.Prompt.prompt()))) + fmt.Print(CursorUpN(remainingLines+1) + CursorRightN(place+len(b.Prompt.prompt()))) } } } @@ -422,9 +417,9 @@ func (b *Buffer) Delete() { if b.DisplaySize()%b.LineWidth == 0 { if b.DisplayPos != b.DisplaySize() { remainingLines := (b.DisplaySize() - b.DisplayPos) / b.LineWidth - fmt.Printf(cursorDownN(remainingLines) + CursorBOL + ClearToEOL) + fmt.Print(CursorDownN(remainingLines) + CursorBOL + ClearToEOL) place := b.DisplayPos % b.LineWidth - fmt.Printf(cursorUpN(remainingLines) + cursorRightN(place+len(b.Prompt.prompt()))) + fmt.Print(CursorUpN(remainingLines) + CursorRightN(place+len(b.Prompt.prompt()))) } } } @@ -471,17 +466,17 @@ func (b *Buffer) DeleteWord() { } func (b *Buffer) ClearScreen() { - fmt.Printf(ClearScreen + CursorReset + b.Prompt.prompt()) + fmt.Print(ClearScreen + CursorReset + b.Prompt.prompt()) if b.IsEmpty() { ph := b.Prompt.placeholder() - fmt.Printf(ColorGrey + ph + cursorLeftN(len(ph)) + ColorDefault) + fmt.Print(ColorGrey + ph + CursorLeftN(len(ph)) + ColorDefault) } else { currPos := b.DisplayPos currIndex := b.Pos b.Pos = 0 b.DisplayPos = 0 b.drawRemaining() - fmt.Printf(CursorReset + cursorRightN(len(b.Prompt.prompt()))) + fmt.Print(CursorReset + CursorRightN(len(b.Prompt.prompt()))) if currPos > 0 { targetLine := currPos / b.LineWidth if targetLine > 0 { @@ -491,10 +486,10 @@ func (b *Buffer) ClearScreen() { } remainder := currPos % b.LineWidth if remainder > 0 { - fmt.Print(cursorRightN(remainder)) + fmt.Print(CursorRightN(remainder)) } if currPos%b.LineWidth == 0 { - fmt.Printf(CursorBOL + b.Prompt.AltPrompt) + fmt.Print(CursorBOL + b.Prompt.AltPrompt) } } b.Pos = currIndex @@ -513,13 +508,13 @@ func (b *Buffer) Replace(r []rune) { b.Buf.Clear() - fmt.Printf(CursorBOL + ClearToEOL) + fmt.Print(CursorBOL + ClearToEOL) for range lineNums { fmt.Print(CursorUp + CursorBOL + ClearToEOL) } - fmt.Printf(CursorBOL + b.Prompt.prompt()) + fmt.Print(CursorBOL + b.Prompt.prompt()) for _, c := range r { b.Add(c) @@ -545,19 +540,3 @@ func (b *Buffer) StringNM(n, m int) string { } return s } - -func cursorLeftN(n int) string { - return fmt.Sprintf(CursorLeftN, n) -} - -func cursorRightN(n int) string { - return fmt.Sprintf(CursorRightN, n) -} - -func cursorUpN(n int) string { - return fmt.Sprintf(CursorUpN, n) -} - -func cursorDownN(n int) string { - return fmt.Sprintf(CursorDownN, n) -} diff --git a/readline/readline.go b/readline/readline.go index e90a5e01f..1c14fe103 100644 --- a/readline/readline.go +++ b/readline/readline.go @@ -98,7 +98,7 @@ func (i *Instance) Readline() (string, error) { showPlaceholder := !i.Pasting || i.Prompt.UseAlt if buf.IsEmpty() && showPlaceholder { ph := i.Prompt.placeholder() - fmt.Printf(ColorGrey + ph + fmt.Sprintf(CursorLeftN, len(ph)) + ColorDefault) + fmt.Print(ColorGrey + ph + CursorLeftN(len(ph)) + ColorDefault) } r, err := i.Terminal.Read() diff --git a/readline/types.go b/readline/types.go index 3b88588f4..e136d9962 100644 --- a/readline/types.go +++ b/readline/types.go @@ -1,5 +1,7 @@ package readline +import "strconv" + const ( CharNull = 0 CharLineStart = 1 @@ -41,34 +43,49 @@ const ( ) const ( - CursorUp = "\033[1A" - CursorDown = "\033[1B" - CursorRight = "\033[1C" - CursorLeft = "\033[1D" + Esc = "\x1b" - CursorSave = "\033[s" - CursorRestore = "\033[u" + CursorSave = Esc + "[s" + CursorRestore = Esc + "[u" - CursorUpN = "\033[%dA" - CursorDownN = "\033[%dB" - CursorRightN = "\033[%dC" - CursorLeftN = "\033[%dD" + CursorEOL = Esc + "[E" + CursorBOL = Esc + "[1G" + CursorHide = Esc + "[?25l" + CursorShow = Esc + "[?25h" - CursorEOL = "\033[E" - CursorBOL = "\033[1G" - CursorHide = "\033[?25l" - CursorShow = "\033[?25h" + ClearToEOL = Esc + "[K" + ClearLine = Esc + "[2K" + ClearScreen = Esc + "[2J" + CursorReset = Esc + "[0;0f" - ClearToEOL = "\033[K" - ClearLine = "\033[2K" - ClearScreen = "\033[2J" - CursorReset = "\033[0;0f" + ColorGrey = Esc + "[38;5;245m" + ColorDefault = Esc + "[0m" - ColorGrey = "\033[38;5;245m" - ColorDefault = "\033[0m" + StartBracketedPaste = Esc + "[?2004h" + EndBracketedPaste = Esc + "[?2004l" +) - StartBracketedPaste = "\033[?2004h" - EndBracketedPaste = "\033[?2004l" +func CursorUpN(n int) string { + return Esc + "[" + strconv.Itoa(n) + "A" +} + +func CursorDownN(n int) string { + return Esc + "[" + strconv.Itoa(n) + "B" +} + +func CursorRightN(n int) string { + return Esc + "[" + strconv.Itoa(n) + "C" +} + +func CursorLeftN(n int) string { + return Esc + "[" + strconv.Itoa(n) + "D" +} + +var ( + CursorUp = CursorUpN(1) + CursorDown = CursorDownN(1) + CursorRight = CursorRightN(1) + CursorLeft = CursorLeftN(1) ) const ( diff --git a/server/sched.go b/server/sched.go index c378865b0..9947fd325 100644 --- a/server/sched.go +++ b/server/sched.go @@ -418,7 +418,7 @@ func (s *Scheduler) load(req *LlmRequest, ggml *llm.GGML, gpus gpu.GpuInfoList, // some older models are not compatible with newer versions of llama.cpp // show a generalized compatibility error until there is a better way to // check for model compatibility - if errors.Is(llm.ErrUnsupportedFormat, err) || strings.Contains(err.Error(), "failed to load model") { + if errors.Is(err, llm.ErrUnsupportedFormat) || strings.Contains(err.Error(), "failed to load model") { err = fmt.Errorf("%v: this model may be incompatible with your version of Ollama. If you previously pulled this model, try updating it by running `ollama pull %s`", err, req.model.ShortName) } slog.Info("NewLlamaServer failed", "model", req.model.ModelPath, "error", err) From eda8a32a0936c1aec120b3c544e402cbba7b7eb7 Mon Sep 17 00:00:00 2001 From: Bruce MacDonald Date: Tue, 13 Aug 2024 23:39:18 +0000 Subject: [PATCH 244/384] update chatml template format to latest in docs (#6344) --- docs/template.md | 6 ------ 1 file changed, 6 deletions(-) diff --git a/docs/template.md b/docs/template.md index f6ce06ba4..1d7104dea 100644 --- a/docs/template.md +++ b/docs/template.md @@ -112,15 +112,9 @@ Keep the following tips and best practices in mind when working with Go template ChatML is a popular template format. It can be used for models such as Databrick's DBRX, Intel's Neural Chat, and Microsoft's Orca 2. ```gotmpl -{{- if .System }}<|im_start|>system -{{ .System }}<|im_end|> -{{ end }} {{- range .Messages }}<|im_start|>{{ .Role }} {{ .Content }}<|im_end|> {{ end }}<|im_start|>assistant -{{ else }} -{{ if .System }}<|im_start|>system -{{ .System }}<|im_end|> ``` ### Example Tools From 8e1050f366e5451651f8385fa570b78b9c7d21cc Mon Sep 17 00:00:00 2001 From: Blake Mizerany Date: Tue, 13 Aug 2024 16:47:35 -0700 Subject: [PATCH 245/384] server: reduce max connections used in download (#6347) The previous value of 64 was WAY too high and unnecessary. It reached diminishing returns and blew past it. This is a more reasonable number for _most_ normal cases. For users on cloud servers with excellent network quality, this will keep screaming for them, without hitting our CDN limits. For users with relatively poor network quality, this will keep them from saturating their network and causing other issues. --- server/download.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/download.go b/server/download.go index 1bca86bf6..02f7ae881 100644 --- a/server/download.go +++ b/server/download.go @@ -94,7 +94,7 @@ func (p *blobDownloadPart) UnmarshalJSON(b []byte) error { } const ( - numDownloadParts = 64 + numDownloadParts = 16 minDownloadPartSize int64 = 100 * format.MegaByte maxDownloadPartSize int64 = 1000 * format.MegaByte ) From 0a8d6ea86d54bbda9d701c38e4279a9c5c204cd9 Mon Sep 17 00:00:00 2001 From: longtao <39115651+eust-w@users.noreply.github.com> Date: Wed, 14 Aug 2024 08:54:19 +0800 Subject: [PATCH 246/384] Fix typo and improve readability (#5964) * Fix typo and improve readability Summary: * Rename updatAvailableMenuID to updateAvailableMenuID * Replace unused cmd parameter with _ in RunServer function * Fix typos in comments (cherry picked from commit 5b8715f0b04773369e8eb1f9e6737995a0ab3ba7) * Update api/client.go Co-authored-by: Jeffrey Morgan --------- Co-authored-by: Jeffrey Morgan --- api/client.go | 4 ++-- app/tray/wintray/menus.go | 14 +++++++------- cmd/cmd.go | 2 +- types/model/name.go | 2 +- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/api/client.go b/api/client.go index bbdf8202f..2528fb21f 100644 --- a/api/client.go +++ b/api/client.go @@ -298,7 +298,7 @@ func (c *Client) List(ctx context.Context) (*ListResponse, error) { return &lr, nil } -// List running models. +// ListRunning lists running models. func (c *Client) ListRunning(ctx context.Context) (*ProcessResponse, error) { var lr ProcessResponse if err := c.do(ctx, http.MethodGet, "/api/ps", nil, &lr); err != nil { @@ -333,7 +333,7 @@ func (c *Client) Show(ctx context.Context, req *ShowRequest) (*ShowResponse, err return &resp, nil } -// Hearbeat checks if the server has started and is responsive; if yes, it +// Heartbeat checks if the server has started and is responsive; if yes, it // returns nil, otherwise an error. func (c *Client) Heartbeat(ctx context.Context) error { if err := c.do(ctx, http.MethodHead, "/", nil, nil); err != nil { diff --git a/app/tray/wintray/menus.go b/app/tray/wintray/menus.go index 9cb3b8933..596244442 100644 --- a/app/tray/wintray/menus.go +++ b/app/tray/wintray/menus.go @@ -11,12 +11,12 @@ import ( ) const ( - updatAvailableMenuID = 1 - updateMenuID = updatAvailableMenuID + 1 - separatorMenuID = updateMenuID + 1 - diagLogsMenuID = separatorMenuID + 1 - diagSeparatorMenuID = diagLogsMenuID + 1 - quitMenuID = diagSeparatorMenuID + 1 + updateAvailableMenuID = 1 + updateMenuID = updateAvailableMenuID + 1 + separatorMenuID = updateMenuID + 1 + diagLogsMenuID = separatorMenuID + 1 + diagSeparatorMenuID = diagLogsMenuID + 1 + quitMenuID = diagSeparatorMenuID + 1 ) func (t *winTray) initMenus() error { @@ -35,7 +35,7 @@ func (t *winTray) initMenus() error { func (t *winTray) UpdateAvailable(ver string) error { if !t.updateNotified { slog.Debug("updating menu and sending notification for new update") - if err := t.addOrUpdateMenuItem(updatAvailableMenuID, 0, updateAvailableMenuTitle, true); err != nil { + if err := t.addOrUpdateMenuItem(updateAvailableMenuID, 0, updateAvailableMenuTitle, true); err != nil { return fmt.Errorf("unable to create menu entries %w", err) } if err := t.addOrUpdateMenuItem(updateMenuID, 0, updateMenutTitle, false); err != nil { diff --git a/cmd/cmd.go b/cmd/cmd.go index 2356110ee..fd7246c8d 100644 --- a/cmd/cmd.go +++ b/cmd/cmd.go @@ -1125,7 +1125,7 @@ func generate(cmd *cobra.Command, opts runOptions) error { return nil } -func RunServer(cmd *cobra.Command, _ []string) error { +func RunServer(_ *cobra.Command, _ []string) error { if err := initializeKeypair(); err != nil { return err } diff --git a/types/model/name.go b/types/model/name.go index 018cb2f5f..75b35ef78 100644 --- a/types/model/name.go +++ b/types/model/name.go @@ -219,7 +219,7 @@ func (n Name) String() string { return b.String() } -// DisplayShort returns a short string version of the name. +// DisplayShortest returns a short string version of the name. func (n Name) DisplayShortest() string { var sb strings.Builder From 8200c371aed68dec5c74e869491ee8e5749ba1eb Mon Sep 17 00:00:00 2001 From: Jeffrey Morgan Date: Wed, 14 Aug 2024 15:19:50 -0700 Subject: [PATCH 247/384] add `CONTRIBUTING.md` (#6349) --- CONTRIBUTING.md | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 CONTRIBUTING.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 000000000..7f12a0fc8 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,37 @@ +# Contributing to Ollama + +Thank you for your interest in contributing to Ollama! Here are a few guidelines to help get you started. + +## Set up + +See the [development documentation](./docs/development.md) for instructions on how to build and run Ollama locally. + +## Pull requests + +### Ideal issues + +* [Bugs](https://github.com/ollama/ollama/issues?q=is%3Aissue+is%3Aopen+label%3Abug): issues where Ollama stops working or where it results in an unexpected error. +* [Performance](https://github.com/ollama/ollama/issues?q=is%3Aissue+is%3Aopen+label%3Aperformance): issues to make Ollama faster at model inference, downloading or uploading. +* [Security](https://github.com/ollama/ollama/blob/main/SECURITY.md): issues that could lead to a security vulnerability. As mentioned in [SECURITY.md](https://github.com/ollama/ollama/blob/main/SECURITY.md), please do not disclose security vulnerabilities publicly. + +### Issues that are harder to review + +* New features: new features (e.g. API fields, environment variables) add surface area to Ollama and make it harder to maintain in the long run as they cannot be removed without potentially breaking users in the future. +* Refactoring: large code improvements are important, but can be harder or take longer to review and merge. +* Documentation: small updates to fill in or dorrect missing documentation is helpful, however large documentation additions can be hard to maintain over time. + +### Issues that may not be accepted + +* Changes that break backwards compatibility in Ollama's API (including the OpenAI-compatible API) +* Changes that add significant friction to the user experience +* Changes that create a large future maintenance burden for maintainers and contributors + +### Best practices + +* Commit messages: please leave both a title and a description in your commit messages. The title should be a short summary of the changes, with a leading word that explains the section of the code being changed (e.g. `api: fix parsing of prompt field`) . In the description, leave a short 2-3 sentences that explain more about the change and its impact. +* Tests: please add test coverage to changes where possible. +* Minimize dependencies: avoid adding new dependencies unless absolutely necessary. + +## Need help? + +If you need help with anything, feel free to reach out to us on our [Discord server](https://discord.gg/ollama). From b3f75fc812fc1559090a7fd9739bd203817a5979 Mon Sep 17 00:00:00 2001 From: Michael Yang Date: Wed, 14 Aug 2024 14:37:51 -0700 Subject: [PATCH 248/384] fix noprune --- server/images.go | 63 ++++++++++++++++++------------------------------ 1 file changed, 24 insertions(+), 39 deletions(-) diff --git a/server/images.go b/server/images.go index 0e753f566..798ed8189 100644 --- a/server/images.go +++ b/server/images.go @@ -215,25 +215,20 @@ func GetManifest(mp ModelPath) (*Manifest, string, error) { return nil, "", err } - if _, err = os.Stat(fp); err != nil { - return nil, "", err - } - - var manifest *Manifest - - bts, err := os.ReadFile(fp) + f, err := os.Open(fp) if err != nil { - return nil, "", fmt.Errorf("couldn't open file '%s'", fp) + return nil, "", err } + defer f.Close() - shaSum := sha256.Sum256(bts) - shaStr := hex.EncodeToString(shaSum[:]) + sha256sum := sha256.New() - if err := json.Unmarshal(bts, &manifest); err != nil { + var manifest Manifest + if err := json.NewDecoder(io.TeeReader(f, sha256sum)).Decode(&manifest); err != nil { return nil, "", err } - return manifest, shaStr, nil + return &manifest, hex.EncodeToString(sha256sum.Sum(nil)), nil } func GetModel(name string) (*Model, error) { @@ -716,7 +711,7 @@ func deleteUnusedLayers(skipModelPath *ModelPath, deleteMap map[string]struct{}) // save (i.e. delete from the deleteMap) any files used in other manifests manifest, _, err := GetManifest(fmp) if err != nil { - return err + return fmt.Errorf("error reading manifest %s: %w", path, err) } for _, layer := range manifest.Layers { @@ -781,8 +776,7 @@ func PruneLayers() error { slog.Info(fmt.Sprintf("total blobs: %d", len(deleteMap))) - err = deleteUnusedLayers(nil, deleteMap) - if err != nil { + if err := deleteUnusedLayers(nil, deleteMap); err != nil { slog.Error(fmt.Sprintf("couldn't remove unused layers: %v", err)) return nil } @@ -877,26 +871,19 @@ func PushModel(ctx context.Context, name string, regOpts *registryOptions, fn fu func PullModel(ctx context.Context, name string, regOpts *registryOptions, fn func(api.ProgressResponse)) error { mp := ParseModelPath(name) - var manifest *Manifest - var err error - var noprune string - // build deleteMap to prune unused layers deleteMap := make(map[string]struct{}) - - if !envconfig.NoPrune() { - manifest, _, err = GetManifest(mp) - if err != nil && !errors.Is(err, os.ErrNotExist) { - return err + manifest, _, err := GetManifest(mp) + if errors.Is(err, os.ErrNotExist) { + // noop + } else if err != nil && !errors.Is(err, os.ErrNotExist) { + return err + } else { + for _, l := range manifest.Layers { + deleteMap[l.Digest] = struct{}{} } - - if manifest != nil { - for _, l := range manifest.Layers { - deleteMap[l.Digest] = struct{}{} - } - if manifest.Config.Digest != "" { - deleteMap[manifest.Config.Digest] = struct{}{} - } + if manifest.Config.Digest != "" { + deleteMap[manifest.Config.Digest] = struct{}{} } } @@ -975,11 +962,9 @@ func PullModel(ctx context.Context, name string, regOpts *registryOptions, fn fu return err } - if noprune == "" { - fn(api.ProgressResponse{Status: "removing any unused layers"}) - err = deleteUnusedLayers(nil, deleteMap) - if err != nil { - slog.Error(fmt.Sprintf("couldn't remove unused layers: %v", err)) + if !envconfig.NoPrune() && len(deleteMap) > 0 { + fn(api.ProgressResponse{Status: "removing unused layers"}) + if err := deleteUnusedLayers(nil, deleteMap); err != nil { fn(api.ProgressResponse{Status: fmt.Sprintf("couldn't remove unused layers: %v", err)}) } } @@ -1000,12 +985,12 @@ func pullModelManifest(ctx context.Context, mp ModelPath, regOpts *registryOptio } defer resp.Body.Close() - var m *Manifest + var m Manifest if err := json.NewDecoder(resp.Body).Decode(&m); err != nil { return nil, err } - return m, err + return &m, err } // GetSHA256Digest returns the SHA256 hash of a given buffer and returns it, and the size of buffer From 237dccba1edb41bb65ed1ffc6eafdd40dd6085e4 Mon Sep 17 00:00:00 2001 From: Michael Yang Date: Wed, 14 Aug 2024 16:36:07 -0700 Subject: [PATCH 249/384] skip invalid manifest files --- server/images.go | 35 +++++------------------------------ server/manifest.go | 2 +- 2 files changed, 6 insertions(+), 31 deletions(-) diff --git a/server/images.go b/server/images.go index 798ed8189..8b3a67cf2 100644 --- a/server/images.go +++ b/server/images.go @@ -687,43 +687,18 @@ func CopyModel(src, dst model.Name) error { return err } -func deleteUnusedLayers(skipModelPath *ModelPath, deleteMap map[string]struct{}) error { - fp, err := GetManifestPath() +func deleteUnusedLayers(deleteMap map[string]struct{}) error { + manifests, err := Manifests() if err != nil { return err } - walkFunc := func(path string, info os.FileInfo, _ error) error { - if info.IsDir() { - return nil - } - - dir, file := filepath.Split(path) - dir = strings.Trim(strings.TrimPrefix(dir, fp), string(os.PathSeparator)) - tag := strings.Join([]string{dir, file}, ":") - fmp := ParseModelPath(tag) - - // skip the manifest we're trying to delete - if skipModelPath != nil && skipModelPath.GetFullTagname() == fmp.GetFullTagname() { - return nil - } - - // save (i.e. delete from the deleteMap) any files used in other manifests - manifest, _, err := GetManifest(fmp) - if err != nil { - return fmt.Errorf("error reading manifest %s: %w", path, err) - } - + for _, manifest := range manifests { for _, layer := range manifest.Layers { delete(deleteMap, layer.Digest) } delete(deleteMap, manifest.Config.Digest) - return nil - } - - if err := filepath.Walk(fp, walkFunc); err != nil { - return err } // only delete the files which are still in the deleteMap @@ -776,7 +751,7 @@ func PruneLayers() error { slog.Info(fmt.Sprintf("total blobs: %d", len(deleteMap))) - if err := deleteUnusedLayers(nil, deleteMap); err != nil { + if err := deleteUnusedLayers(deleteMap); err != nil { slog.Error(fmt.Sprintf("couldn't remove unused layers: %v", err)) return nil } @@ -964,7 +939,7 @@ func PullModel(ctx context.Context, name string, regOpts *registryOptions, fn fu if !envconfig.NoPrune() && len(deleteMap) > 0 { fn(api.ProgressResponse{Status: "removing unused layers"}) - if err := deleteUnusedLayers(nil, deleteMap); err != nil { + if err := deleteUnusedLayers(deleteMap); err != nil { fn(api.ProgressResponse{Status: fmt.Sprintf("couldn't remove unused layers: %v", err)}) } } diff --git a/server/manifest.go b/server/manifest.go index 6a5d7b885..0f19641de 100644 --- a/server/manifest.go +++ b/server/manifest.go @@ -150,7 +150,7 @@ func Manifests() (map[model.Name]*Manifest, error) { n := model.ParseNameFromFilepath(rel) if !n.IsValid() { - slog.Warn("bad manifest name", "path", rel, "error", err) + slog.Warn("bad manifest name", "path", rel) continue } From 3a75e74e34c976d596437c8aa14587ada562301e Mon Sep 17 00:00:00 2001 From: Michael Yang Date: Thu, 15 Aug 2024 10:29:14 -0700 Subject: [PATCH 250/384] only skip invalid json manifests --- server/manifest.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/server/manifest.go b/server/manifest.go index 0f19641de..6b04753ff 100644 --- a/server/manifest.go +++ b/server/manifest.go @@ -5,6 +5,7 @@ import ( "encoding/hex" "encoding/json" "errors" + "fmt" "io" "log/slog" "os" @@ -155,9 +156,11 @@ func Manifests() (map[model.Name]*Manifest, error) { } m, err := ParseNamedManifest(n) - if err != nil { + if syntax := &(json.SyntaxError{}); errors.As(err, &syntax) { slog.Warn("bad manifest", "name", n, "error", err) continue + } else if err != nil { + return nil, fmt.Errorf("%s: %w", n, err) } ms[n] = m From a84c05cf9140c2eb288a6c7b56bb1c592bbaacc7 Mon Sep 17 00:00:00 2001 From: eust-w Date: Fri, 16 Aug 2024 06:00:12 +0800 Subject: [PATCH 251/384] fix: Add tooltip to system tray icon - Updated setIcon method to include tooltip text for the system tray icon. - Added NIF_TIP flag and set the tooltip text using UTF16 encoding. Resolves: #6372 --- app/tray/wintray/tray.go | 8 +++++++- app/tray/wintray/w32api.go | 1 + 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/app/tray/wintray/tray.go b/app/tray/wintray/tray.go index ccd087a17..6f8278939 100644 --- a/app/tray/wintray/tray.go +++ b/app/tray/wintray/tray.go @@ -11,6 +11,7 @@ import ( "path/filepath" "sort" "sync" + "syscall" "unsafe" "golang.org/x/sys/windows" @@ -433,7 +434,12 @@ func (t *winTray) setIcon(src string) error { t.muNID.Lock() defer t.muNID.Unlock() t.nid.Icon = h - t.nid.Flags |= NIF_ICON + t.nid.Flags |= NIF_ICON | NIF_TIP + if toolTipUTF16, err := syscall.UTF16FromString(commontray.ToolTip); err == nil { + copy(t.nid.Tip[:], toolTipUTF16) + } else { + return err + } t.nid.Size = uint32(unsafe.Sizeof(*t.nid)) return t.nid.modify() diff --git a/app/tray/wintray/w32api.go b/app/tray/wintray/w32api.go index a1e0381de..7c7c0ac8a 100644 --- a/app/tray/wintray/w32api.go +++ b/app/tray/wintray/w32api.go @@ -61,6 +61,7 @@ const ( MIIM_SUBMENU = 0x00000004 MIM_APPLYTOSUBMENUS = 0x80000000 NIF_ICON = 0x00000002 + NIF_TIP = 0x00000004 NIF_INFO = 0x00000010 NIF_MESSAGE = 0x00000001 SW_HIDE = 0 From bdc4308afb72d47ce63583427f810b02d569d58a Mon Sep 17 00:00:00 2001 From: zwwhdls Date: Fri, 16 Aug 2024 11:43:19 +0800 Subject: [PATCH 252/384] fix: chmod new layer to 0o644 when creating it Signed-off-by: zwwhdls --- server/layer.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/server/layer.go b/server/layer.go index c666bd106..0bdee72b3 100644 --- a/server/layer.go +++ b/server/layer.go @@ -51,6 +51,9 @@ func NewLayer(r io.Reader, mediatype string) (Layer, error) { if err := os.Rename(temp.Name(), blob); err != nil { return Layer{}, err } + if err := os.Chmod(blob, 0o644); err != nil { + return Layer{}, err + } } return Layer{ From 0ad0e738cd7ed1266b3c210ad54dcd2b70142563 Mon Sep 17 00:00:00 2001 From: Richard Lyons Date: Sun, 18 Aug 2024 01:43:26 +0200 Subject: [PATCH 253/384] Override numParallel only if unset. --- server/sched.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/server/sched.go b/server/sched.go index 9947fd325..4d9c02969 100644 --- a/server/sched.go +++ b/server/sched.go @@ -734,7 +734,9 @@ func pickBestFullFitByLibrary(req *LlmRequest, ggml *llm.GGML, gpus gpu.GpuInfoL // If multiple Libraries are detected, pick the Library which loads the most layers for the model func pickBestPartialFitByLibrary(req *LlmRequest, ggml *llm.GGML, gpus gpu.GpuInfoList, numParallel *int) gpu.GpuInfoList { - *numParallel = 1 + if *numParallel <= 0 { + *numParallel = 1 + } byLibrary := gpus.ByLibrary() if len(byLibrary) <= 1 { return gpus From 9352eeb752531decccc7c6b91a07bc3dd5efa67e Mon Sep 17 00:00:00 2001 From: Richard Lyons Date: Sun, 18 Aug 2024 02:55:01 +0200 Subject: [PATCH 254/384] Reset NumCtx. --- server/sched.go | 1 + 1 file changed, 1 insertion(+) diff --git a/server/sched.go b/server/sched.go index 4d9c02969..3fe6d7fc9 100644 --- a/server/sched.go +++ b/server/sched.go @@ -736,6 +736,7 @@ func pickBestFullFitByLibrary(req *LlmRequest, ggml *llm.GGML, gpus gpu.GpuInfoL func pickBestPartialFitByLibrary(req *LlmRequest, ggml *llm.GGML, gpus gpu.GpuInfoList, numParallel *int) gpu.GpuInfoList { if *numParallel <= 0 { *numParallel = 1 + req.opts.NumCtx = req.origNumCtx } byLibrary := gpus.ByLibrary() if len(byLibrary) <= 1 { From 885cf45087863aa2e064a05da99e8bd07d69970a Mon Sep 17 00:00:00 2001 From: Richard Lyons Date: Sun, 18 Aug 2024 03:07:16 +0200 Subject: [PATCH 255/384] Fix white space. --- server/sched.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/sched.go b/server/sched.go index 3fe6d7fc9..9d8c41446 100644 --- a/server/sched.go +++ b/server/sched.go @@ -736,8 +736,8 @@ func pickBestFullFitByLibrary(req *LlmRequest, ggml *llm.GGML, gpus gpu.GpuInfoL func pickBestPartialFitByLibrary(req *LlmRequest, ggml *llm.GGML, gpus gpu.GpuInfoList, numParallel *int) gpu.GpuInfoList { if *numParallel <= 0 { *numParallel = 1 - req.opts.NumCtx = req.origNumCtx - } + req.opts.NumCtx = req.origNumCtx + } byLibrary := gpus.ByLibrary() if len(byLibrary) <= 1 { return gpus From 9fddef3731842bd8f40d217da6b84ab7ef5dfe97 Mon Sep 17 00:00:00 2001 From: Jeffrey Morgan Date: Mon, 19 Aug 2024 09:20:52 -0700 Subject: [PATCH 256/384] server: limit upload parts to 16 (#6411) --- server/upload.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/upload.go b/server/upload.go index 2f115436e..020e89551 100644 --- a/server/upload.go +++ b/server/upload.go @@ -45,7 +45,7 @@ type blobUpload struct { } const ( - numUploadParts = 64 + numUploadParts = 16 minUploadPartSize int64 = 100 * format.MegaByte maxUploadPartSize int64 = 1000 * format.MegaByte ) From 74d45f010276c2f2653f3ca8c4f76cb0552fb46e Mon Sep 17 00:00:00 2001 From: Daniel Hiltgen Date: Mon, 8 Jul 2024 12:50:11 -0700 Subject: [PATCH 257/384] Refactor linux packaging This adjusts linux to follow a similar model to windows with a discrete archive (zip/tgz) to cary the primary executable, and dependent libraries. Runners are still carried as payloads inside the main binary Darwin retain the payload model where the go binary is fully self contained. --- .github/workflows/release.yaml | 1 - Dockerfile | 29 ++++++------ app/ollama.iss | 11 +---- envconfig/config.go | 4 +- gpu/amd_common.go | 2 +- gpu/amd_windows.go | 2 +- gpu/gpu.go | 50 ++++++++++++++------- gpu/gpu_linux.go | 2 +- llm/ext_server/CMakeLists.txt | 3 +- llm/generate/gen_common.sh | 17 ++++++- llm/generate/gen_linux.sh | 81 ++++++++++++++++------------------ llm/generate/gen_windows.ps1 | 43 +++++++++--------- llm/server.go | 12 +++-- scripts/build_linux.sh | 10 ++--- scripts/build_windows.ps1 | 12 ++--- scripts/install.sh | 31 ++++++++++--- 16 files changed, 171 insertions(+), 139 deletions(-) diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 5ae630c31..9287f6f75 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -363,7 +363,6 @@ jobs: - run: | ./scripts/build_linux.sh ./scripts/build_docker.sh - mv dist/deps/* dist/ - uses: actions/upload-artifact@v4 with: name: dist-linux-amd64 diff --git a/Dockerfile b/Dockerfile index c8efdd8a2..120ddc219 100644 --- a/Dockerfile +++ b/Dockerfile @@ -18,6 +18,7 @@ ENV PATH /opt/rh/devtoolset-10/root/usr/bin:$PATH COPY --from=llm-code / /go/src/github.com/ollama/ollama/ WORKDIR /go/src/github.com/ollama/ollama/llm/generate ARG CGO_CFLAGS +ENV GOARCH amd64 RUN OLLAMA_SKIP_STATIC_GENERATE=1 OLLAMA_SKIP_CPU_GENERATE=1 sh gen_linux.sh FROM --platform=linux/arm64 nvidia/cuda:$CUDA_VERSION-devel-rockylinux8 AS cuda-build-arm64 @@ -28,6 +29,7 @@ ENV PATH /opt/rh/gcc-toolset-10/root/usr/bin:$PATH COPY --from=llm-code / /go/src/github.com/ollama/ollama/ WORKDIR /go/src/github.com/ollama/ollama/llm/generate ARG CGO_CFLAGS +ENV GOARCH arm64 RUN OLLAMA_SKIP_STATIC_GENERATE=1 OLLAMA_SKIP_CPU_GENERATE=1 sh gen_linux.sh FROM --platform=linux/amd64 rocm/dev-centos-7:${ROCM_VERSION}-complete AS rocm-build-amd64 @@ -40,15 +42,10 @@ COPY --from=llm-code / /go/src/github.com/ollama/ollama/ WORKDIR /go/src/github.com/ollama/ollama/llm/generate ARG CGO_CFLAGS ARG AMDGPU_TARGETS +ENV GOARCH amd64 RUN OLLAMA_SKIP_STATIC_GENERATE=1 OLLAMA_SKIP_CPU_GENERATE=1 sh gen_linux.sh -RUN mkdir /tmp/scratch && \ - for dep in $(zcat /go/src/github.com/ollama/ollama/llm/build/linux/x86_64/rocm*/bin/deps.txt.gz) ; do \ - cp ${dep} /tmp/scratch/ || exit 1 ; \ - done && \ - (cd /opt/rocm/lib && tar cf - rocblas/library) | (cd /tmp/scratch/ && tar xf - ) && \ - mkdir -p /go/src/github.com/ollama/ollama/dist/deps/ && \ - (cd /tmp/scratch/ && tar czvf /go/src/github.com/ollama/ollama/dist/deps/ollama-linux-amd64-rocm.tgz . ) - +RUN mkdir -p ../../dist/linux-amd64/ollama_libs && \ + (cd /opt/rocm/lib && tar cf - rocblas/library) | (cd ../../dist/linux-amd64/ollama_libs && tar xf - ) FROM --platform=linux/amd64 centos:7 AS cpu-builder-amd64 ARG CMAKE_VERSION @@ -59,6 +56,7 @@ ENV PATH /opt/rh/devtoolset-10/root/usr/bin:$PATH COPY --from=llm-code / /go/src/github.com/ollama/ollama/ ARG OLLAMA_CUSTOM_CPU_DEFS ARG CGO_CFLAGS +ENV GOARCH amd64 WORKDIR /go/src/github.com/ollama/ollama/llm/generate FROM --platform=linux/amd64 cpu-builder-amd64 AS static-build-amd64 @@ -79,6 +77,7 @@ ENV PATH /opt/rh/gcc-toolset-10/root/usr/bin:$PATH COPY --from=llm-code / /go/src/github.com/ollama/ollama/ ARG OLLAMA_CUSTOM_CPU_DEFS ARG CGO_CFLAGS +ENV GOARCH arm64 WORKDIR /go/src/github.com/ollama/ollama/llm/generate FROM --platform=linux/arm64 cpu-builder-arm64 AS static-build-arm64 @@ -95,12 +94,13 @@ COPY . . COPY --from=static-build-amd64 /go/src/github.com/ollama/ollama/llm/build/linux/ llm/build/linux/ COPY --from=cpu_avx-build-amd64 /go/src/github.com/ollama/ollama/llm/build/linux/ llm/build/linux/ COPY --from=cpu_avx2-build-amd64 /go/src/github.com/ollama/ollama/llm/build/linux/ llm/build/linux/ +COPY --from=cuda-build-amd64 /go/src/github.com/ollama/ollama/dist/ dist/ COPY --from=cuda-build-amd64 /go/src/github.com/ollama/ollama/llm/build/linux/ llm/build/linux/ +COPY --from=rocm-build-amd64 /go/src/github.com/ollama/ollama/dist/ dist/ COPY --from=rocm-build-amd64 /go/src/github.com/ollama/ollama/llm/build/linux/ llm/build/linux/ -COPY --from=rocm-build-amd64 /go/src/github.com/ollama/ollama/dist/deps/ ./dist/deps/ ARG GOFLAGS ARG CGO_CFLAGS -RUN go build -trimpath . +RUN go build -trimpath -o dist/linux-amd64/ollama . # Intermediate stage used for ./scripts/build_linux.sh FROM --platform=linux/arm64 cpu-build-arm64 AS build-arm64 @@ -109,23 +109,24 @@ ARG GOLANG_VERSION WORKDIR /go/src/github.com/ollama/ollama COPY . . COPY --from=static-build-arm64 /go/src/github.com/ollama/ollama/llm/build/linux/ llm/build/linux/ +COPY --from=cuda-build-arm64 /go/src/github.com/ollama/ollama/dist/ dist/ COPY --from=cuda-build-arm64 /go/src/github.com/ollama/ollama/llm/build/linux/ llm/build/linux/ ARG GOFLAGS ARG CGO_CFLAGS -RUN go build -trimpath . +RUN go build -trimpath -o dist/linux-arm64/ollama . # Runtime stages FROM --platform=linux/amd64 ubuntu:22.04 as runtime-amd64 RUN apt-get update && apt-get install -y ca-certificates -COPY --from=build-amd64 /go/src/github.com/ollama/ollama/ollama /bin/ollama +COPY --from=build-amd64 /go/src/github.com/ollama/ollama/dist/linux-amd64/ollama /bin/ollama FROM --platform=linux/arm64 ubuntu:22.04 as runtime-arm64 RUN apt-get update && apt-get install -y ca-certificates -COPY --from=build-arm64 /go/src/github.com/ollama/ollama/ollama /bin/ollama +COPY --from=build-arm64 /go/src/github.com/ollama/ollama/dist/linux-arm64/ollama /bin/ollama # Radeon images are much larger so we keep it distinct from the CPU/CUDA image FROM --platform=linux/amd64 rocm/dev-centos-7:${ROCM_VERSION}-complete as runtime-rocm RUN update-pciids -COPY --from=build-amd64 /go/src/github.com/ollama/ollama/ollama /bin/ollama +COPY --from=build-amd64 /go/src/github.com/ollama/ollama/dist/linux-amd64/ollama /bin/ollama EXPOSE 11434 ENV OLLAMA_HOST 0.0.0.0 diff --git a/app/ollama.iss b/app/ollama.iss index dc6178f7b..e9cf48ec6 100644 --- a/app/ollama.iss +++ b/app/ollama.iss @@ -91,16 +91,7 @@ Source: "..\ollama.exe"; DestDir: "{app}"; Flags: ignoreversion 64bit Source: "..\dist\windows-{#ARCH}\ollama_runners\*"; DestDir: "{app}\ollama_runners"; Flags: ignoreversion 64bit recursesubdirs Source: "..\dist\ollama_welcome.ps1"; DestDir: "{app}"; Flags: ignoreversion Source: ".\assets\app.ico"; DestDir: "{app}"; Flags: ignoreversion -#if DirExists("..\dist\windows-amd64\cuda") - Source: "..\dist\windows-amd64\cuda\*"; DestDir: "{app}\cuda\"; Flags: ignoreversion recursesubdirs -#endif -#if DirExists("..\dist\windows-amd64\oneapi") - Source: "..\dist\windows-amd64\oneapi\*"; DestDir: "{app}\oneapi\"; Flags: ignoreversion recursesubdirs -#endif -#if DirExists("..\dist\windows-amd64\rocm") - Source: "..\dist\windows-amd64\rocm\*"; DestDir: "{app}\rocm\"; Flags: ignoreversion recursesubdirs -#endif - +Source: "..\dist\windows-amd64\ollama_libs\*"; DestDir: "{app}\ollama_libs\"; Flags: ignoreversion recursesubdirs [Icons] Name: "{group}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; IconFilename: "{app}\app.ico" diff --git a/envconfig/config.go b/envconfig/config.go index b82b773d4..7f0976c07 100644 --- a/envconfig/config.go +++ b/envconfig/config.go @@ -193,8 +193,8 @@ func RunnersDir() (p string) { for _, root := range []string{filepath.Dir(exe), cwd} { paths = append(paths, root, - filepath.Join(root, "windows-"+runtime.GOARCH), - filepath.Join(root, "dist", "windows-"+runtime.GOARCH), + filepath.Join(root, runtime.GOOS+"-"+runtime.GOARCH), + filepath.Join(root, "dist", runtime.GOOS+"-"+runtime.GOARCH), ) } diff --git a/gpu/amd_common.go b/gpu/amd_common.go index 2839cb7c3..05747208f 100644 --- a/gpu/amd_common.go +++ b/gpu/amd_common.go @@ -54,7 +54,7 @@ func commonAMDValidateLibDir() (string, error) { // Installer payload location if we're running the installed binary exe, err := os.Executable() if err == nil { - rocmTargetDir := filepath.Join(filepath.Dir(exe), "rocm") + rocmTargetDir := filepath.Join(filepath.Dir(exe), "ollama_libs") if rocmLibUsable(rocmTargetDir) { slog.Debug("detected ROCM next to ollama executable " + rocmTargetDir) return rocmTargetDir, nil diff --git a/gpu/amd_windows.go b/gpu/amd_windows.go index edabeb43a..5d25a966f 100644 --- a/gpu/amd_windows.go +++ b/gpu/amd_windows.go @@ -153,7 +153,7 @@ func AMDValidateLibDir() (string, error) { // Installer payload (if we're running from some other location) localAppData := os.Getenv("LOCALAPPDATA") appDir := filepath.Join(localAppData, "Programs", "Ollama") - rocmTargetDir := filepath.Join(appDir, "rocm") + rocmTargetDir := filepath.Join(appDir, "ollama_libs") if rocmLibUsable(rocmTargetDir) { slog.Debug("detected ollama installed ROCm at " + rocmTargetDir) return rocmTargetDir, nil diff --git a/gpu/gpu.go b/gpu/gpu.go index dc124a3ed..d0ae0f34f 100644 --- a/gpu/gpu.go +++ b/gpu/gpu.go @@ -229,11 +229,7 @@ func GetGPUInfo() GpuInfoList { return GpuInfoList{cpus[0].GpuInfo} } - // On windows we bundle the nvidia library one level above the runner dir - depPath := "" - if runtime.GOOS == "windows" && envconfig.RunnersDir() != "" { - depPath = filepath.Join(filepath.Dir(envconfig.RunnersDir()), "cuda") - } + depPath := GetDepDir() // Load ALL libraries cHandles = initCudaHandles() @@ -306,13 +302,6 @@ func GetGPUInfo() GpuInfoList { if envconfig.IntelGPU() { oHandles = initOneAPIHandles() if oHandles != nil && oHandles.oneapi != nil { - - // On windows we bundle the oneapi library one level above the runner dir - depPath = "" - if runtime.GOOS == "windows" && envconfig.RunnersDir() != "" { - depPath = filepath.Join(filepath.Dir(envconfig.RunnersDir()), "oneapi") - } - for d := range oHandles.oneapi.num_drivers { if oHandles.oneapi == nil { // shouldn't happen @@ -467,10 +456,12 @@ func GetGPUInfo() GpuInfoList { func FindGPULibs(baseLibName string, defaultPatterns []string) []string { // Multiple GPU libraries may exist, and some may not work, so keep trying until we exhaust them var ldPaths []string - var patterns []string gpuLibPaths := []string{} slog.Debug("Searching for GPU library", "name", baseLibName) + // Start with our bundled libraries + patterns := []string{filepath.Join(GetDepDir(), baseLibName)} + switch runtime.GOOS { case "windows": ldPaths = strings.Split(os.Getenv("PATH"), ";") @@ -479,13 +470,14 @@ func FindGPULibs(baseLibName string, defaultPatterns []string) []string { default: return gpuLibPaths } - // Start with whatever we find in the PATH/LD_LIBRARY_PATH + + // Then with whatever we find in the PATH/LD_LIBRARY_PATH for _, ldPath := range ldPaths { d, err := filepath.Abs(ldPath) if err != nil { continue } - patterns = append(patterns, filepath.Join(d, baseLibName+"*")) + patterns = append(patterns, filepath.Join(d, baseLibName)) } patterns = append(patterns, defaultPatterns...) slog.Debug("gpu library search", "globs", patterns) @@ -641,3 +633,31 @@ func (l GpuInfoList) GetVisibleDevicesEnv() (string, string) { return "", "" } } + +func GetDepDir() string { + // On Windows/linux we bundle the dependencies at the same level as the executable + appExe, err := os.Executable() + if err != nil { + slog.Warn("failed to lookup executable path", "error", err) + } + cwd, err := os.Getwd() + if err != nil { + slog.Warn("failed to lookup working directory", "error", err) + } + // Scan for any of our dependeices, and pick first match + for _, root := range []string{filepath.Dir(appExe), cwd} { + libDep := "ollama_libs" + if _, err := os.Stat(filepath.Join(root, libDep)); err == nil { + return filepath.Join(root, libDep) + } + // Developer mode, local build + if _, err := os.Stat(filepath.Join(root, runtime.GOOS+"-"+runtime.GOARCH, libDep)); err == nil { + return filepath.Join(root, runtime.GOOS+"-"+runtime.GOARCH, libDep) + } + if _, err := os.Stat(filepath.Join(root, "dist", runtime.GOOS+"-"+runtime.GOARCH, libDep)); err == nil { + return filepath.Join(root, "dist", runtime.GOOS+"-"+runtime.GOARCH, libDep) + } + } + slog.Warn("unable to locate gpu dependency libraries") + return "" +} diff --git a/gpu/gpu_linux.go b/gpu/gpu_linux.go index d6d2675c8..d4d20bc4f 100644 --- a/gpu/gpu_linux.go +++ b/gpu/gpu_linux.go @@ -47,7 +47,7 @@ var ( CudartMgmtName = "libcudart.so*" NvcudaMgmtName = "libcuda.so*" NvmlMgmtName = "" // not currently wired on linux - OneapiMgmtName = "libze_intel_gpu.so" + OneapiMgmtName = "libze_intel_gpu.so*" ) func GetCPUMem() (memInfo, error) { diff --git a/llm/ext_server/CMakeLists.txt b/llm/ext_server/CMakeLists.txt index bfc97c63d..90fd0ef25 100644 --- a/llm/ext_server/CMakeLists.txt +++ b/llm/ext_server/CMakeLists.txt @@ -1,12 +1,13 @@ set(TARGET ollama_llama_server) option(LLAMA_SERVER_VERBOSE "Build verbose logging option for Server" ON) +set(LLAMA_SERVER_LDFLAGS $ENV{LLAMA_SERVER_LDFLAGS}) include_directories(${CMAKE_CURRENT_SOURCE_DIR}) add_executable(${TARGET} server.cpp utils.hpp json.hpp httplib.h) install(TARGETS ${TARGET} RUNTIME) target_compile_definitions(${TARGET} PRIVATE SERVER_VERBOSE=$ ) -target_link_libraries(${TARGET} PRIVATE ggml llama common llava ${CMAKE_THREAD_LIBS_INIT}) +target_link_libraries(${TARGET} PRIVATE ggml llama common llava ${CMAKE_THREAD_LIBS_INIT} ${LLAMA_SERVER_LDFLAGS}) if (WIN32) TARGET_LINK_LIBRARIES(${TARGET} PRIVATE ws2_32) endif() diff --git a/llm/generate/gen_common.sh b/llm/generate/gen_common.sh index da1b06882..f1541f2ac 100644 --- a/llm/generate/gen_common.sh +++ b/llm/generate/gen_common.sh @@ -9,11 +9,14 @@ init_vars() { ARCH="arm64" ;; *) - ARCH=$(uname -m | sed -e "s/aarch64/arm64/g") + echo "GOARCH must be set" + echo "this script is meant to be run from within go generate" + exit 1 + ;; esac LLAMACPP_DIR=../llama.cpp - CMAKE_DEFS="" + CMAKE_DEFS="-DCMAKE_SKIP_RPATH=on" CMAKE_TARGETS="--target ollama_llama_server" if echo "${CGO_CFLAGS}" | grep -- '-g' >/dev/null; then CMAKE_DEFS="-DCMAKE_BUILD_TYPE=RelWithDebInfo -DCMAKE_VERBOSE_MAKEFILE=on -DLLAMA_GPROF=on -DLLAMA_SERVER_VERBOSE=on ${CMAKE_DEFS}" @@ -27,6 +30,7 @@ init_vars() { WHOLE_ARCHIVE="-Wl,-force_load" NO_WHOLE_ARCHIVE="" GCC_ARCH="-arch ${ARCH}" + DIST_BASE=../../dist/darwin-${GOARCH}/ ;; "Linux") LIB_EXT="so" @@ -35,6 +39,7 @@ init_vars() { # Cross compiling not supported on linux - Use docker GCC_ARCH="" + DIST_BASE=../../dist/linux-${GOARCH}/ ;; *) ;; @@ -105,6 +110,14 @@ compress() { echo "Finished compression" } +install() { + echo "Installing libraries to bin dir ${BUILD_DIR}/bin/" + for lib in $(find ${BUILD_DIR} -name \*.${LIB_EXT}); do + rm -f "${BUILD_DIR}/bin/$(basename ${lib})" + cp -af "${lib}" "${BUILD_DIR}/bin/" + done +} + # Keep the local tree clean after we're done with the build cleanup() { (cd ${LLAMACPP_DIR}/ && git checkout CMakeLists.txt) diff --git a/llm/generate/gen_linux.sh b/llm/generate/gen_linux.sh index db2c6c30c..70fc03139 100755 --- a/llm/generate/gen_linux.sh +++ b/llm/generate/gen_linux.sh @@ -51,7 +51,7 @@ if [ -z "${CUDACXX}" ]; then export CUDACXX=$(command -v nvcc) fi fi -COMMON_CMAKE_DEFS="-DBUILD_SHARED_LIBS=off -DCMAKE_POSITION_INDEPENDENT_CODE=on -DGGML_NATIVE=off -DGGML_AVX=on -DGGML_AVX2=off -DGGML_AVX512=off -DGGML_FMA=off -DGGML_F16C=off -DGGML_OPENMP=off" +COMMON_CMAKE_DEFS="-DCMAKE_SKIP_RPATH=on -DBUILD_SHARED_LIBS=on -DCMAKE_POSITION_INDEPENDENT_CODE=on -DGGML_NATIVE=off -DGGML_AVX=on -DGGML_AVX2=off -DGGML_AVX512=off -DGGML_FMA=off -DGGML_F16C=off -DGGML_OPENMP=off" source $(dirname $0)/gen_common.sh init_vars git_module_setup @@ -77,10 +77,11 @@ if [ -z "${OLLAMA_SKIP_CPU_GENERATE}" ]; then if [ -n "${OLLAMA_CUSTOM_CPU_DEFS}" ]; then init_vars echo "OLLAMA_CUSTOM_CPU_DEFS=\"${OLLAMA_CUSTOM_CPU_DEFS}\"" - CMAKE_DEFS="${OLLAMA_CUSTOM_CPU_DEFS} -DBUILD_SHARED_LIBS=off -DCMAKE_POSITION_INDEPENDENT_CODE=on ${CMAKE_DEFS}" + CMAKE_DEFS="${OLLAMA_CUSTOM_CPU_DEFS} -DBUILD_SHARED_LIBS=on -DCMAKE_POSITION_INDEPENDENT_CODE=on ${CMAKE_DEFS}" BUILD_DIR="../build/linux/${ARCH}/cpu" echo "Building custom CPU" build + install compress else # Darwin Rosetta x86 emulation does NOT support AVX, AVX2, AVX512 @@ -93,7 +94,7 @@ if [ -z "${OLLAMA_SKIP_CPU_GENERATE}" ]; then # -DGGML_AVX512_VBMI -- 2018 Intel Cannon Lake # -DGGML_AVX512_VNNI -- 2021 Intel Alder Lake - COMMON_CPU_DEFS="-DBUILD_SHARED_LIBS=off -DCMAKE_POSITION_INDEPENDENT_CODE=on -DGGML_NATIVE=off -DGGML_OPENMP=off" + COMMON_CPU_DEFS="-DBUILD_SHARED_LIBS=on -DCMAKE_POSITION_INDEPENDENT_CODE=on -DGGML_NATIVE=off -DGGML_OPENMP=off" if [ -z "${OLLAMA_CPU_TARGET}" -o "${OLLAMA_CPU_TARGET}" = "cpu" ]; then # # CPU first for the default library, set up as lowest common denominator for maximum compatibility (including Rosetta) @@ -103,6 +104,7 @@ if [ -z "${OLLAMA_SKIP_CPU_GENERATE}" ]; then BUILD_DIR="../build/linux/${ARCH}/cpu" echo "Building LCD CPU" build + install compress fi @@ -120,6 +122,7 @@ if [ -z "${OLLAMA_SKIP_CPU_GENERATE}" ]; then BUILD_DIR="../build/linux/${ARCH}/cpu_avx" echo "Building AVX CPU" build + install compress fi @@ -133,6 +136,7 @@ if [ -z "${OLLAMA_SKIP_CPU_GENERATE}" ]; then BUILD_DIR="../build/linux/${ARCH}/cpu_avx2" echo "Building AVX2 CPU" build + install compress fi fi @@ -178,29 +182,18 @@ if [ -z "${OLLAMA_SKIP_CUDA_GENERATE}" -a -d "${CUDA_LIB_DIR}" ]; then CMAKE_CUDA_DEFS="-DGGML_CUDA=on -DCMAKE_CUDA_ARCHITECTURES=${CMAKE_CUDA_ARCHITECTURES} ${OLLAMA_CUSTOM_CUDA_DEFS}" echo "Building custom CUDA GPU" else - CMAKE_CUDA_DEFS="-DGGML_CUDA=on -DCMAKE_CUDA_FLAGS=-t8 -DCMAKE_CUDA_ARCHITECTURES=${CMAKE_CUDA_ARCHITECTURES}" + CMAKE_CUDA_DEFS="-DGGML_CUDA=on -DCMAKE_CUDA_ARCHITECTURES=${CMAKE_CUDA_ARCHITECTURES}" fi - CMAKE_DEFS="${COMMON_CMAKE_DEFS} ${CMAKE_DEFS} ${ARM64_DEFS} ${CMAKE_CUDA_DEFS}" + export CUDAFLAGS="-t8" + CMAKE_DEFS="${COMMON_CMAKE_DEFS} ${CMAKE_DEFS} ${ARM64_DEFS} ${CMAKE_CUDA_DEFS} -DGGML_STATIC=off" BUILD_DIR="../build/linux/${ARCH}/cuda${CUDA_VARIANT}" - EXTRA_LIBS="-L${CUDA_LIB_DIR} -lcudart -lcublas -lcublasLt -lcuda" + export LLAMA_SERVER_LDFLAGS="-L${CUDA_LIB_DIR} -lcudart -lcublas -lcublasLt -lcuda" + CUDA_DIST_DIR="${DIST_BASE}/ollama_libs" build - - # Carry the CUDA libs as payloads to help reduce dependency burden on users - # - # TODO - in the future we may shift to packaging these separately and conditionally - # downloading them in the install script. - DEPS="$(ldd ${BUILD_DIR}/bin/ollama_llama_server )" - for lib in libcudart.so libcublas.so libcublasLt.so ; do - DEP=$(echo "${DEPS}" | grep ${lib} | cut -f1 -d' ' | xargs || true) - if [ -n "${DEP}" -a -e "${CUDA_LIB_DIR}/${DEP}" ]; then - cp "${CUDA_LIB_DIR}/${DEP}" "${BUILD_DIR}/bin/" - elif [ -e "${CUDA_LIB_DIR}/${lib}.${CUDA_MAJOR}" ]; then - cp "${CUDA_LIB_DIR}/${lib}.${CUDA_MAJOR}" "${BUILD_DIR}/bin/" - elif [ -e "${CUDART_LIB_DIR}/${lib}" ]; then - cp -d ${CUDART_LIB_DIR}/${lib}* "${BUILD_DIR}/bin/" - else - cp -d "${CUDA_LIB_DIR}/${lib}*" "${BUILD_DIR}/bin/" - fi + install + mkdir -p "${CUDA_DIST_DIR}" + for lib in ${CUDA_LIB_DIR}/libcudart.so* ${CUDA_LIB_DIR}/libcublas.so* ${CUDA_LIB_DIR}/libcublasLt.so* ; do + cp -a "${lib}" "${CUDA_DIST_DIR}" done compress @@ -218,21 +211,24 @@ if [ -z "${OLLAMA_SKIP_ONEAPI_GENERATE}" -a -d "${ONEAPI_ROOT}" ]; then CC=icx CMAKE_DEFS="${COMMON_CMAKE_DEFS} ${CMAKE_DEFS} -DCMAKE_C_COMPILER=icx -DCMAKE_CXX_COMPILER=icpx -DGGML_SYCL=ON -DGGML_SYCL_F16=OFF" BUILD_DIR="../build/linux/${ARCH}/oneapi" - EXTRA_LIBS="-fsycl -Wl,-rpath,${ONEAPI_ROOT}/compiler/latest/lib,-rpath,${ONEAPI_ROOT}/mkl/latest/lib,-rpath,${ONEAPI_ROOT}/tbb/latest/lib,-rpath,${ONEAPI_ROOT}/compiler/latest/opt/oclfpga/linux64/lib -lOpenCL -lmkl_core -lmkl_sycl_blas -lmkl_intel_ilp64 -lmkl_tbb_thread -ltbb" + ONEAPI_DIST_DIR="${DIST_BASE}/ollama_libs" + export LLAMA_SERVER_LDFLAGS="-fsycl -lOpenCL -lmkl_core -lmkl_sycl_blas -lmkl_intel_ilp64 -lmkl_tbb_thread -ltbb" DEBUG_FLAGS="" # icx compiles with -O0 if we pass -g, so we must remove it build # copy oneAPI dependencies + mkdir -p "${ONEAPI_DIST_DIR}" for dep in $(ldd "${BUILD_DIR}/bin/ollama_llama_server" | grep "=>" | cut -f2 -d= | cut -f2 -d' ' | grep -e sycl -e mkl -e tbb); do - cp "${dep}" "${BUILD_DIR}/bin/" + cp -a "${dep}" "${ONEAPI_DIST_DIR}" done - cp "${ONEAPI_ROOT}/compiler/latest/lib/libOpenCL.so" "${BUILD_DIR}/bin/" - cp "${ONEAPI_ROOT}/compiler/latest/lib/libimf.so" "${BUILD_DIR}/bin/" - cp "${ONEAPI_ROOT}/compiler/latest/lib/libintlc.so.5" "${BUILD_DIR}/bin/" - cp "${ONEAPI_ROOT}/compiler/latest/lib/libirng.so" "${BUILD_DIR}/bin/" - cp "${ONEAPI_ROOT}/compiler/latest/lib/libpi_level_zero.so" "${BUILD_DIR}/bin/" - cp "${ONEAPI_ROOT}/compiler/latest/lib/libsvml.so" "${BUILD_DIR}/bin/" - cp "${ONEAPI_ROOT}/compiler/latest/lib/libur_loader.so.0" "${BUILD_DIR}/bin/" + cp "${ONEAPI_ROOT}/compiler/latest/lib/libOpenCL.so" "${ONEAPI_DIST_DIR}" + cp "${ONEAPI_ROOT}/compiler/latest/lib/libimf.so" "${ONEAPI_DIST_DIR}" + cp "${ONEAPI_ROOT}/compiler/latest/lib/libintlc.so.5" "${ONEAPI_DIST_DIR}" + cp "${ONEAPI_ROOT}/compiler/latest/lib/libirng.so" "${ONEAPI_DIST_DIR}" + cp "${ONEAPI_ROOT}/compiler/latest/lib/libpi_level_zero.so" "${ONEAPI_DIST_DIR}" + cp "${ONEAPI_ROOT}/compiler/latest/lib/libsvml.so" "${ONEAPI_DIST_DIR}" + cp "${ONEAPI_ROOT}/compiler/latest/lib/libur_loader.so.0" "${ONEAPI_DIST_DIR}" + install compress fi @@ -262,21 +258,18 @@ if [ -z "${OLLAMA_SKIP_ROCM_GENERATE}" -a -d "${ROCM_PATH}" ]; then echo "Building custom ROCM GPU" fi BUILD_DIR="../build/linux/${ARCH}/rocm${ROCM_VARIANT}" - EXTRA_LIBS="-L${ROCM_PATH}/lib -L/opt/amdgpu/lib/x86_64-linux-gnu/ -Wl,-rpath,\$ORIGIN/../../rocm/ -lhipblas -lrocblas -lamdhip64 -lrocsolver -lamd_comgr -lhsa-runtime64 -lrocsparse -ldrm -ldrm_amdgpu" + ROCM_DIST_DIR="${DIST_BASE}/ollama_libs" + # TODO figure out how to disable runpath (rpath) + # export CMAKE_HIP_FLAGS="-fno-rtlib-add-rpath" # doesn't work + export LLAMA_SERVER_LDFLAGS="-L${ROCM_PATH}/lib -L/opt/amdgpu/lib/x86_64-linux-gnu/ -lhipblas -lrocblas -lamdhip64 -lrocsolver -lamd_comgr -lhsa-runtime64 -lrocsparse -ldrm -ldrm_amdgpu" build - # Record the ROCM dependencies - rm -f "${BUILD_DIR}/bin/deps.txt" - touch "${BUILD_DIR}/bin/deps.txt" - for dep in $(ldd "${BUILD_DIR}/bin/ollama_llama_server" | grep "=>" | cut -f2 -d= | cut -f2 -d' ' | grep -e rocm -e amdgpu -e libtinfo ); do - echo "${dep}" >> "${BUILD_DIR}/bin/deps.txt" + # copy the ROCM dependencies + mkdir -p "${ROCM_DIST_DIR}" + for dep in $(ldd "${BUILD_DIR}/bin/ollama_llama_server" | grep "=>" | cut -f2 -d= | cut -f2 -d' ' | grep -v "${ARCH}/rocm${ROCM_VARIANT}" | grep -e rocm -e amdgpu -e libtinfo ); do + cp -a "${dep}"* "${ROCM_DIST_DIR}" done - # bomb out if for some reason we didn't get a few deps - if [ $(cat "${BUILD_DIR}/bin/deps.txt" | wc -l ) -lt 8 ] ; then - cat "${BUILD_DIR}/bin/deps.txt" - echo "ERROR: deps file short" - exit 1 - fi + install compress fi diff --git a/llm/generate/gen_windows.ps1 b/llm/generate/gen_windows.ps1 index d8bce92d6..1f8c96d8f 100644 --- a/llm/generate/gen_windows.ps1 +++ b/llm/generate/gen_windows.ps1 @@ -286,12 +286,11 @@ function build_cuda() { sign install - rm -ea 0 -recurse -force -path "${script:SRC_DIR}\dist\windows-${script:ARCH}\cuda\" - md "${script:SRC_DIR}\dist\windows-${script:ARCH}\cuda\" -ea 0 > $null - write-host "copying CUDA dependencies to ${script:SRC_DIR}\dist\windows-${script:ARCH}\cuda\" - cp "${script:CUDA_LIB_DIR}\cudart64_*.dll" "${script:SRC_DIR}\dist\windows-${script:ARCH}\cuda\" - cp "${script:CUDA_LIB_DIR}\cublas64_*.dll" "${script:SRC_DIR}\dist\windows-${script:ARCH}\cuda\" - cp "${script:CUDA_LIB_DIR}\cublasLt64_*.dll" "${script:SRC_DIR}\dist\windows-${script:ARCH}\cuda\" + md "${script:SRC_DIR}\dist\windows-${script:ARCH}\ollama_libs\" -ea 0 > $null + write-host "copying CUDA dependencies to ${script:SRC_DIR}\dist\windows-${script:ARCH}\ollama_libs\" + cp "${script:CUDA_LIB_DIR}\cudart64_*.dll" "${script:SRC_DIR}\dist\windows-${script:ARCH}\ollama_libs\" + cp "${script:CUDA_LIB_DIR}\cublas64_*.dll" "${script:SRC_DIR}\dist\windows-${script:ARCH}\ollama_libs\" + cp "${script:CUDA_LIB_DIR}\cublasLt64_*.dll" "${script:SRC_DIR}\dist\windows-${script:ARCH}\ollama_libs\" } else { write-host "Skipping CUDA generation step" } @@ -325,18 +324,17 @@ function build_oneapi() { sign install - rm -ea 0 -recurse -force -path "${script:SRC_DIR}\dist\windows-${script:ARCH}\oneapi\" - md "${script:SRC_DIR}\dist\windows-${script:ARCH}\oneapi\" -ea 0 > $null - cp "${env:ONEAPI_ROOT}\compiler\latest\bin\libirngmd.dll" "${script:SRC_DIR}\dist\windows-${script:ARCH}\oneapi\" - cp "${env:ONEAPI_ROOT}\compiler\latest\bin\libmmd.dll" "${script:SRC_DIR}\dist\windows-${script:ARCH}\oneapi\" - cp "${env:ONEAPI_ROOT}\compiler\latest\bin\pi_level_zero.dll" "${script:SRC_DIR}\dist\windows-${script:ARCH}\oneapi\" - cp "${env:ONEAPI_ROOT}\compiler\latest\bin\pi_unified_runtime.dll" "${script:SRC_DIR}\dist\windows-${script:ARCH}\oneapi\" - cp "${env:ONEAPI_ROOT}\compiler\latest\bin\pi_win_proxy_loader.dll" "${script:SRC_DIR}\dist\windows-${script:ARCH}\oneapi\" - cp "${env:ONEAPI_ROOT}\compiler\latest\bin\svml_dispmd.dll" "${script:SRC_DIR}\dist\windows-${script:ARCH}\oneapi\" - cp "${env:ONEAPI_ROOT}\compiler\latest\bin\sycl7.dll" "${script:SRC_DIR}\dist\windows-${script:ARCH}\oneapi\" - cp "${env:ONEAPI_ROOT}\mkl\latest\bin\mkl_core.2.dll" "${script:SRC_DIR}\dist\windows-${script:ARCH}\oneapi\" - cp "${env:ONEAPI_ROOT}\mkl\latest\bin\mkl_sycl_blas.4.dll" "${script:SRC_DIR}\dist\windows-${script:ARCH}\oneapi\" - cp "${env:ONEAPI_ROOT}\mkl\latest\bin\mkl_tbb_thread.2.dll" "${script:SRC_DIR}\dist\windows-${script:ARCH}\oneapi\" + md "${script:SRC_DIR}\dist\windows-${script:ARCH}\ollama_libs\" -ea 0 > $null + cp "${env:ONEAPI_ROOT}\compiler\latest\bin\libirngmd.dll" "${script:SRC_DIR}\dist\windows-${script:ARCH}\ollama_libs\" + cp "${env:ONEAPI_ROOT}\compiler\latest\bin\libmmd.dll" "${script:SRC_DIR}\dist\windows-${script:ARCH}\ollama_libs\" + cp "${env:ONEAPI_ROOT}\compiler\latest\bin\pi_level_zero.dll" "${script:SRC_DIR}\dist\windows-${script:ARCH}\ollama_libs\" + cp "${env:ONEAPI_ROOT}\compiler\latest\bin\pi_unified_runtime.dll" "${script:SRC_DIR}\dist\windows-${script:ARCH}\ollama_libs\" + cp "${env:ONEAPI_ROOT}\compiler\latest\bin\pi_win_proxy_loader.dll" "${script:SRC_DIR}\dist\windows-${script:ARCH}\ollama_libs\" + cp "${env:ONEAPI_ROOT}\compiler\latest\bin\svml_dispmd.dll" "${script:SRC_DIR}\dist\windows-${script:ARCH}\ollama_libs\" + cp "${env:ONEAPI_ROOT}\compiler\latest\bin\sycl7.dll" "${script:SRC_DIR}\dist\windows-${script:ARCH}\ollama_libs\" + cp "${env:ONEAPI_ROOT}\mkl\latest\bin\mkl_core.2.dll" "${script:SRC_DIR}\dist\windows-${script:ARCH}\ollama_libs\" + cp "${env:ONEAPI_ROOT}\mkl\latest\bin\mkl_sycl_blas.4.dll" "${script:SRC_DIR}\dist\windows-${script:ARCH}\ollama_libs\" + cp "${env:ONEAPI_ROOT}\mkl\latest\bin\mkl_tbb_thread.2.dll" "${script:SRC_DIR}\dist\windows-${script:ARCH}\ollama_libs\" } else { Write-Host "Skipping oneAPI generation step" } @@ -386,12 +384,11 @@ function build_rocm() { sign install - rm -ea 0 -recurse -force -path "${script:SRC_DIR}\dist\windows-${script:ARCH}\rocm\" - md "${script:SRC_DIR}\dist\windows-${script:ARCH}\rocm\rocblas\library\" -ea 0 > $null - cp "${env:HIP_PATH}\bin\hipblas.dll" "${script:SRC_DIR}\dist\windows-${script:ARCH}\rocm\" - cp "${env:HIP_PATH}\bin\rocblas.dll" "${script:SRC_DIR}\dist\windows-${script:ARCH}\rocm\" + md "${script:SRC_DIR}\dist\windows-${script:ARCH}\ollama_libs\rocblas\library\" -ea 0 > $null + cp "${env:HIP_PATH}\bin\hipblas.dll" "${script:SRC_DIR}\dist\windows-${script:ARCH}\ollama_libs\" + cp "${env:HIP_PATH}\bin\rocblas.dll" "${script:SRC_DIR}\dist\windows-${script:ARCH}\ollama_libs\" # amdhip64.dll dependency comes from the driver and must be installed on the host to use AMD GPUs - cp "${env:HIP_PATH}\bin\rocblas\library\*" "${script:SRC_DIR}\dist\windows-${script:ARCH}\rocm\rocblas\library\" + cp "${env:HIP_PATH}\bin\rocblas\library\*" "${script:SRC_DIR}\dist\windows-${script:ARCH}\ollama_libs\rocblas\library\" } else { write-host "Skipping ROCm generation step" } diff --git a/llm/server.go b/llm/server.go index d2b8db9b3..9347a4589 100644 --- a/llm/server.go +++ b/llm/server.go @@ -306,20 +306,18 @@ func NewLlamaServer(gpus gpu.GpuInfoList, model string, ggml *GGML, adapters, pr if runtime.GOOS == "windows" { pathEnv = "PATH" } - // prepend the server directory to LD_LIBRARY_PATH/PATH and the parent dir for common dependencies - libraryPaths := []string{dir, filepath.Dir(dir)} + // Start with the server directory for the LD_LIBRARY_PATH/PATH + libraryPaths := []string{dir} if libraryPath, ok := os.LookupEnv(pathEnv); ok { - // Append our runner directory to the path - // This will favor system libraries over our bundled library dependencies + // favor our bundled library dependencies over system libraries libraryPaths = append(libraryPaths, filepath.SplitList(libraryPath)...) } // Note: we always put the dependency path first - // since this was the exact version we verified for AMD GPUs - // and we favor what the user had in their path + // since this was the exact version we compiled/linked against if gpus[0].DependencyPath != "" { - // TODO refine for multi-gpu support + // assume gpus from the same library have the same dependency path libraryPaths = append([]string{gpus[0].DependencyPath}, libraryPaths...) } diff --git a/scripts/build_linux.sh b/scripts/build_linux.sh index 27c4ff1f8..4ea512294 100755 --- a/scripts/build_linux.sh +++ b/scripts/build_linux.sh @@ -21,11 +21,9 @@ for TARGETARCH in ${BUILD_ARCH}; do -t builder:$TARGETARCH \ . docker create --platform linux/$TARGETARCH --name builder-$TARGETARCH builder:$TARGETARCH - docker cp builder-$TARGETARCH:/go/src/github.com/ollama/ollama/ollama ./dist/ollama-linux-$TARGETARCH - - if [ "$TARGETARCH" = "amd64" ]; then - docker cp builder-$TARGETARCH:/go/src/github.com/ollama/ollama/dist/deps/ ./dist/ - fi - + docker cp builder-$TARGETARCH:/go/src/github.com/ollama/ollama/dist/linux-$TARGETARCH ./dist docker rm builder-$TARGETARCH + echo "Compressing final linux bundle..." + rm -f ./dist/ollama-linux-$TARGETARCH.tgz + (cd dist/linux-$TARGETARCH && tar cf - . | gzip --best > ../ollama-linux-$TARGETARCH.tgz ) done diff --git a/scripts/build_windows.ps1 b/scripts/build_windows.ps1 index edc737593..e8d851f4f 100644 --- a/scripts/build_windows.ps1 +++ b/scripts/build_windows.ps1 @@ -103,22 +103,22 @@ function buildApp() { function gatherDependencies() { write-host "Gathering runtime dependencies" cd "${script:SRC_DIR}" - md "${script:DEPS_DIR}\ollama_runners" -ea 0 > $null + md "${script:DEPS_DIR}\ollama_libs" -ea 0 > $null # TODO - this varies based on host build system and MSVC version - drive from dumpbin output # currently works for Win11 + MSVC 2019 + Cuda V11 - cp "${env:VCToolsRedistDir}\x64\Microsoft.VC*.CRT\msvcp140*.dll" "${script:DEPS_DIR}\ollama_runners\" - cp "${env:VCToolsRedistDir}\x64\Microsoft.VC*.CRT\vcruntime140.dll" "${script:DEPS_DIR}\ollama_runners\" - cp "${env:VCToolsRedistDir}\x64\Microsoft.VC*.CRT\vcruntime140_1.dll" "${script:DEPS_DIR}\ollama_runners\" + cp "${env:VCToolsRedistDir}\x64\Microsoft.VC*.CRT\msvcp140*.dll" "${script:DEPS_DIR}\ollama_libs\" + cp "${env:VCToolsRedistDir}\x64\Microsoft.VC*.CRT\vcruntime140.dll" "${script:DEPS_DIR}\ollama_libs\" + cp "${env:VCToolsRedistDir}\x64\Microsoft.VC*.CRT\vcruntime140_1.dll" "${script:DEPS_DIR}\ollama_libs\" foreach ($part in $("runtime", "stdio", "filesystem", "math", "convert", "heap", "string", "time", "locale", "environment")) { - cp "$env:VCToolsRedistDir\..\..\..\Tools\Llvm\x64\bin\api-ms-win-crt-${part}*.dll" "${script:DEPS_DIR}\ollama_runners\" + cp "$env:VCToolsRedistDir\..\..\..\Tools\Llvm\x64\bin\api-ms-win-crt-${part}*.dll" "${script:DEPS_DIR}\ollama_libs\" } cp "${script:SRC_DIR}\app\ollama_welcome.ps1" "${script:SRC_DIR}\dist\" if ("${env:KEY_CONTAINER}") { write-host "about to sign" - foreach ($file in (get-childitem "${script:DEPS_DIR}\cuda\cu*.dll") + @("${script:SRC_DIR}\dist\ollama_welcome.ps1")){ + foreach ($file in (get-childitem "${script:DEPS_DIR}\ollama_libs\cu*.dll") + @("${script:SRC_DIR}\dist\ollama_welcome.ps1")){ write-host "signing $file" & "${script:SignTool}" sign /v /fd sha256 /t http://timestamp.digicert.com /f "${script:OLLAMA_CERT}" ` /csp "Google Cloud KMS Provider" /kc ${env:KEY_CONTAINER} $file diff --git a/scripts/install.sh b/scripts/install.sh index 03af5a69e..f0439b003 100644 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -63,16 +63,32 @@ if [ -n "$NEEDS" ]; then exit 1 fi -status "Downloading ollama..." -curl --fail --show-error --location --progress-bar -o $TEMP_DIR/ollama "https://ollama.com/download/ollama-linux-${ARCH}${VER_PARAM}" - for BINDIR in /usr/local/bin /usr/bin /bin; do echo $PATH | grep -q $BINDIR && break || continue done +OLLAMA_INSTALL_DIR=${OLLAMA_INSTALL_DIR:-${BINDIR}} -status "Installing ollama to $BINDIR..." +status "Installing ollama to $OLLAMA_INSTALL_DIR" $SUDO install -o0 -g0 -m755 -d $BINDIR -$SUDO install -o0 -g0 -m755 $TEMP_DIR/ollama $BINDIR/ollama +$SUDO install -o0 -g0 -m755 -d "$OLLAMA_INSTALL_DIR" +if curl -I --silent --fail --location "https://ollama.com/download/ollama-linux-${ARCH}.tgz${VER_PARAM}" >/dev/null ; then + status "Downloading Linux ${ARCH} bundle" + curl --fail --show-error --location --progress-bar \ + "https://ollama.com/download/ollama-linux-${ARCH}.tgz${VER_PARAM}" | \ + $SUDO tar -xzf - -C "$OLLAMA_INSTALL_DIR" + BUNDLE=1 +else + status "Downloading Linux ${ARCH} CLI" + curl --fail --show-error --location --progress-bar -o "$TEMP_DIR/ollama"\ + "https://ollama.com/download/ollama-linux-${ARCH}${VER_PARAM}" + $SUDO install -o0 -g0 -m755 $TEMP_DIR/ollama $OLLAMA_INSTALL_DIR/ollama + BUNDLE=0 +fi + +if [ "$OLLAMA_INSTALL_DIR/ollama" != "$BINDIR/ollama" ] ; then + status "Making ollama accessible in the PATH in $BINDIR" + $SUDO ln -sf "$OLLAMA_INSTALL_DIR/ollama" "$BINDIR/ollama" +fi install_success() { status 'The Ollama API is now available at 127.0.0.1:11434.' @@ -178,6 +194,11 @@ if ! check_gpu lspci nvidia && ! check_gpu lshw nvidia && ! check_gpu lspci amdg fi if check_gpu lspci amdgpu || check_gpu lshw amdgpu; then + if [ $BUNDLE -ne 0 ]; then + install_success + status "AMD GPU ready." + exit 0 + fi # Look for pre-existing ROCm v6 before downloading the dependencies for search in "${HIP_PATH:-''}" "${ROCM_PATH:-''}" "/opt/rocm" "/usr/lib64"; do if [ -n "${search}" ] && [ -e "${search}/libhipblas.so.2" -o -e "${search}/lib/libhipblas.so.2" ]; then From c7bcb0031965e33531358639620a11516d101b54 Mon Sep 17 00:00:00 2001 From: Daniel Hiltgen Date: Fri, 9 Aug 2024 07:21:40 -0700 Subject: [PATCH 258/384] Wire up ccache and pigz in the docker based build This should help speed things up a little --- Dockerfile | 37 ++++++++++++++++++++++++++----------- llm/generate/gen_common.sh | 15 +++++++++------ llm/generate/gen_darwin.sh | 2 ++ llm/generate/gen_linux.sh | 2 ++ scripts/build_linux.sh | 3 ++- scripts/rh_linux_deps.sh | 14 ++++++++++++-- 6 files changed, 53 insertions(+), 20 deletions(-) diff --git a/Dockerfile b/Dockerfile index 120ddc219..8eb90057f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -19,7 +19,8 @@ COPY --from=llm-code / /go/src/github.com/ollama/ollama/ WORKDIR /go/src/github.com/ollama/ollama/llm/generate ARG CGO_CFLAGS ENV GOARCH amd64 -RUN OLLAMA_SKIP_STATIC_GENERATE=1 OLLAMA_SKIP_CPU_GENERATE=1 sh gen_linux.sh +RUN --mount=type=cache,target=/root/.ccache \ + OLLAMA_SKIP_STATIC_GENERATE=1 OLLAMA_SKIP_CPU_GENERATE=1 bash gen_linux.sh FROM --platform=linux/arm64 nvidia/cuda:$CUDA_VERSION-devel-rockylinux8 AS cuda-build-arm64 ARG CMAKE_VERSION @@ -30,7 +31,12 @@ COPY --from=llm-code / /go/src/github.com/ollama/ollama/ WORKDIR /go/src/github.com/ollama/ollama/llm/generate ARG CGO_CFLAGS ENV GOARCH arm64 -RUN OLLAMA_SKIP_STATIC_GENERATE=1 OLLAMA_SKIP_CPU_GENERATE=1 sh gen_linux.sh +RUN --mount=type=cache,target=/root/.ccache \ + OLLAMA_SKIP_STATIC_GENERATE=1 \ + OLLAMA_SKIP_CPU_GENERATE=1 \ + CMAKE_CUDA_ARCHITECTURES="${CUDA_V11_ARCHITECTURES}" \ + CUDA_VARIANT="_v11" \ + bash gen_linux.sh FROM --platform=linux/amd64 rocm/dev-centos-7:${ROCM_VERSION}-complete AS rocm-build-amd64 ARG CMAKE_VERSION @@ -43,7 +49,8 @@ WORKDIR /go/src/github.com/ollama/ollama/llm/generate ARG CGO_CFLAGS ARG AMDGPU_TARGETS ENV GOARCH amd64 -RUN OLLAMA_SKIP_STATIC_GENERATE=1 OLLAMA_SKIP_CPU_GENERATE=1 sh gen_linux.sh +RUN --mount=type=cache,target=/root/.ccache \ + OLLAMA_SKIP_STATIC_GENERATE=1 OLLAMA_SKIP_CPU_GENERATE=1 bash gen_linux.sh RUN mkdir -p ../../dist/linux-amd64/ollama_libs && \ (cd /opt/rocm/lib && tar cf - rocblas/library) | (cd ../../dist/linux-amd64/ollama_libs && tar xf - ) @@ -60,13 +67,17 @@ ENV GOARCH amd64 WORKDIR /go/src/github.com/ollama/ollama/llm/generate FROM --platform=linux/amd64 cpu-builder-amd64 AS static-build-amd64 -RUN OLLAMA_CPU_TARGET="static" sh gen_linux.sh +RUN --mount=type=cache,target=/root/.ccache \ + OLLAMA_CPU_TARGET="static" bash gen_linux.sh FROM --platform=linux/amd64 cpu-builder-amd64 AS cpu-build-amd64 -RUN OLLAMA_SKIP_STATIC_GENERATE=1 OLLAMA_CPU_TARGET="cpu" sh gen_linux.sh +RUN --mount=type=cache,target=/root/.ccache \ + OLLAMA_SKIP_STATIC_GENERATE=1 OLLAMA_CPU_TARGET="cpu" bash gen_linux.sh FROM --platform=linux/amd64 cpu-builder-amd64 AS cpu_avx-build-amd64 -RUN OLLAMA_SKIP_STATIC_GENERATE=1 OLLAMA_CPU_TARGET="cpu_avx" sh gen_linux.sh +RUN --mount=type=cache,target=/root/.ccache \ + OLLAMA_SKIP_STATIC_GENERATE=1 OLLAMA_CPU_TARGET="cpu_avx" bash gen_linux.sh FROM --platform=linux/amd64 cpu-builder-amd64 AS cpu_avx2-build-amd64 -RUN OLLAMA_SKIP_STATIC_GENERATE=1 OLLAMA_CPU_TARGET="cpu_avx2" sh gen_linux.sh +RUN --mount=type=cache,target=/root/.ccache \ + OLLAMA_SKIP_STATIC_GENERATE=1 OLLAMA_CPU_TARGET="cpu_avx2" bash gen_linux.sh FROM --platform=linux/arm64 rockylinux:8 AS cpu-builder-arm64 ARG CMAKE_VERSION @@ -81,9 +92,11 @@ ENV GOARCH arm64 WORKDIR /go/src/github.com/ollama/ollama/llm/generate FROM --platform=linux/arm64 cpu-builder-arm64 AS static-build-arm64 -RUN OLLAMA_CPU_TARGET="static" sh gen_linux.sh +RUN --mount=type=cache,target=/root/.ccache \ + OLLAMA_CPU_TARGET="static" bash gen_linux.sh FROM --platform=linux/arm64 cpu-builder-arm64 AS cpu-build-arm64 -RUN OLLAMA_SKIP_STATIC_GENERATE=1 OLLAMA_CPU_TARGET="cpu" sh gen_linux.sh +RUN --mount=type=cache,target=/root/.ccache \ + OLLAMA_SKIP_STATIC_GENERATE=1 OLLAMA_CPU_TARGET="cpu" bash gen_linux.sh # Intermediate stage used for ./scripts/build_linux.sh @@ -100,7 +113,8 @@ COPY --from=rocm-build-amd64 /go/src/github.com/ollama/ollama/dist/ dist/ COPY --from=rocm-build-amd64 /go/src/github.com/ollama/ollama/llm/build/linux/ llm/build/linux/ ARG GOFLAGS ARG CGO_CFLAGS -RUN go build -trimpath -o dist/linux-amd64/ollama . +RUN --mount=type=cache,target=/root/.ccache \ + go build -trimpath -o dist/linux-amd64/ollama . # Intermediate stage used for ./scripts/build_linux.sh FROM --platform=linux/arm64 cpu-build-arm64 AS build-arm64 @@ -113,7 +127,8 @@ COPY --from=cuda-build-arm64 /go/src/github.com/ollama/ollama/dist/ dist/ COPY --from=cuda-build-arm64 /go/src/github.com/ollama/ollama/llm/build/linux/ llm/build/linux/ ARG GOFLAGS ARG CGO_CFLAGS -RUN go build -trimpath -o dist/linux-arm64/ollama . +RUN --mount=type=cache,target=/root/.ccache \ + go build -trimpath -o dist/linux-arm64/ollama . # Runtime stages FROM --platform=linux/amd64 ubuntu:22.04 as runtime-amd64 diff --git a/llm/generate/gen_common.sh b/llm/generate/gen_common.sh index f1541f2ac..40115936d 100644 --- a/llm/generate/gen_common.sh +++ b/llm/generate/gen_common.sh @@ -47,6 +47,7 @@ init_vars() { if [ -z "${CMAKE_CUDA_ARCHITECTURES}" ] ; then CMAKE_CUDA_ARCHITECTURES="50;52;61;70;75;80" fi + GZIP=$(which pigz 2>/dev/null || echo "gzip") } git_module_setup() { @@ -90,21 +91,23 @@ build() { compress() { echo "Compressing payloads to reduce overall binary size..." - pids="" rm -rf ${BUILD_DIR}/bin/*.gz for f in ${BUILD_DIR}/bin/* ; do - gzip -n --best -f ${f} & - pids+=" $!" + ${GZIP} -n --best -f ${f} & + compress_pids+=" $!" done # check for lib directory if [ -d ${BUILD_DIR}/lib ]; then for f in ${BUILD_DIR}/lib/* ; do - gzip -n --best -f ${f} & - pids+=" $!" + ${GZIP} -n --best -f ${f} & + compress_pids+=" $!" done fi echo - for pid in ${pids}; do +} + +wait_for_compress() { + for pid in ${compress_pids}; do wait $pid done echo "Finished compression" diff --git a/llm/generate/gen_darwin.sh b/llm/generate/gen_darwin.sh index 6c0b62cb7..f22c0f8e7 100755 --- a/llm/generate/gen_darwin.sh +++ b/llm/generate/gen_darwin.sh @@ -6,6 +6,7 @@ set -ex set -o pipefail +compress_pids="" echo "Starting darwin generate script" source $(dirname $0)/gen_common.sh init_vars @@ -98,4 +99,5 @@ case "${GOARCH}" in esac cleanup +wait_for_compress echo "go generate completed. LLM runners: $(cd ${BUILD_DIR}/..; echo *)" diff --git a/llm/generate/gen_linux.sh b/llm/generate/gen_linux.sh index 70fc03139..1365d07db 100755 --- a/llm/generate/gen_linux.sh +++ b/llm/generate/gen_linux.sh @@ -13,6 +13,7 @@ set -ex set -o pipefail +compress_pids="" # See https://llvm.org/docs/AMDGPUUsage.html#processors for reference amdGPUs() { @@ -274,4 +275,5 @@ if [ -z "${OLLAMA_SKIP_ROCM_GENERATE}" -a -d "${ROCM_PATH}" ]; then fi cleanup +wait_for_compress echo "go generate completed. LLM runners: $(cd ${BUILD_DIR}/..; echo *)" diff --git a/scripts/build_linux.sh b/scripts/build_linux.sh index 4ea512294..ebb60c5a7 100755 --- a/scripts/build_linux.sh +++ b/scripts/build_linux.sh @@ -4,6 +4,7 @@ set -eu export VERSION=${VERSION:-$(git describe --tags --first-parent --abbrev=7 --long --dirty --always | sed -e "s/^v//g")} export GOFLAGS="'-ldflags=-w -s \"-X=github.com/ollama/ollama/version.Version=$VERSION\" \"-X=github.com/ollama/ollama/server.mode=release\"'" +GZIP=$(which pigz 2>/dev/null || echo "gzip") BUILD_ARCH=${BUILD_ARCH:-"amd64 arm64"} export AMDGPU_TARGETS=${AMDGPU_TARGETS:=""} @@ -25,5 +26,5 @@ for TARGETARCH in ${BUILD_ARCH}; do docker rm builder-$TARGETARCH echo "Compressing final linux bundle..." rm -f ./dist/ollama-linux-$TARGETARCH.tgz - (cd dist/linux-$TARGETARCH && tar cf - . | gzip --best > ../ollama-linux-$TARGETARCH.tgz ) + (cd dist/linux-$TARGETARCH && tar cf - . | ${GZIP} --best > ../ollama-linux-$TARGETARCH.tgz ) done diff --git a/scripts/rh_linux_deps.sh b/scripts/rh_linux_deps.sh index 81648d68e..b4c9afd63 100644 --- a/scripts/rh_linux_deps.sh +++ b/scripts/rh_linux_deps.sh @@ -3,6 +3,7 @@ # Script for common Dockerfile dependency installation in redhat linux based images set -ex +set -o pipefail MACHINE=$(uname -m) if grep -i "centos" /etc/system-release >/dev/null; then @@ -29,7 +30,7 @@ if grep -i "centos" /etc/system-release >/dev/null; then dnf install -y rh-git227-git ln -s /opt/rh/rh-git227/root/usr/bin/git /usr/local/bin/git fi - dnf install -y devtoolset-10-gcc devtoolset-10-gcc-c++ + dnf install -y devtoolset-10-gcc devtoolset-10-gcc-c++ pigz elif grep -i "rocky" /etc/system-release >/dev/null; then # Temporary workaround until rocky 8 AppStream ships GCC 10.4 (10.3 is incompatible with NVCC) cat << EOF > /etc/yum.repos.d/Rocky-Vault.repo @@ -43,12 +44,21 @@ gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-rockyofficial EOF dnf install -y git \ gcc-toolset-10-gcc-10.2.1-8.2.el8 \ - gcc-toolset-10-gcc-c++-10.2.1-8.2.el8 + gcc-toolset-10-gcc-c++-10.2.1-8.2.el8 \ + pigz else echo "ERROR Unexpected distro" exit 1 fi +if [ "${MACHINE}" = "x86_64" ] ; then + curl -s -L https://github.com/ccache/ccache/releases/download/v4.10.2/ccache-4.10.2-linux-x86_64.tar.xz | tar -Jx -C /tmp --strip-components 1 && \ + mv /tmp/ccache /usr/local/bin/ +else + yum -y install epel-release + yum install -y ccache +fi + if [ -n "${CMAKE_VERSION}" ]; then curl -s -L https://github.com/Kitware/CMake/releases/download/v${CMAKE_VERSION}/cmake-${CMAKE_VERSION}-linux-$(uname -m).tar.gz | tar -zx -C /usr --strip-components 1 fi From d470ebe78bc76c098bc378f98f08f7094063ab4d Mon Sep 17 00:00:00 2001 From: Daniel Hiltgen Date: Thu, 30 May 2024 21:54:07 -0700 Subject: [PATCH 259/384] Add Jetson cuda variants for arm This adds new variants for arm64 specific to Jetson platforms --- Dockerfile | 48 +++++++++++++++++++++++++++++++++++---- gpu/gpu.go | 44 +++++++++++++++++++++++++++++++++-- gpu/gpu_darwin.go | 4 ++-- gpu/types.go | 6 ++--- llm/generate/gen_linux.sh | 5 ++-- llm/payload.go | 4 ++-- scripts/build_linux.sh | 1 + 7 files changed, 96 insertions(+), 16 deletions(-) diff --git a/Dockerfile b/Dockerfile index 8eb90057f..79b2a6961 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,6 +3,9 @@ ARG CMAKE_VERSION=3.22.1 # this CUDA_VERSION corresponds with the one specified in docs/gpu.md ARG CUDA_VERSION=11.3.1 ARG ROCM_VERSION=6.1.2 +ARG JETPACK_6=r36.2.0 +ARG JETPACK_5=r35.4.1 +ARG JETPACK_4=r32.7.1 # Copy the minimal context we need to run the generate scripts FROM scratch AS llm-code @@ -22,7 +25,7 @@ ENV GOARCH amd64 RUN --mount=type=cache,target=/root/.ccache \ OLLAMA_SKIP_STATIC_GENERATE=1 OLLAMA_SKIP_CPU_GENERATE=1 bash gen_linux.sh -FROM --platform=linux/arm64 nvidia/cuda:$CUDA_VERSION-devel-rockylinux8 AS cuda-build-arm64 +FROM --platform=linux/arm64 nvidia/cuda:$CUDA_VERSION-devel-rockylinux8 AS cuda-build-server-arm64 ARG CMAKE_VERSION COPY ./scripts/rh_linux_deps.sh / RUN CMAKE_VERSION=${CMAKE_VERSION} sh /rh_linux_deps.sh @@ -31,11 +34,40 @@ COPY --from=llm-code / /go/src/github.com/ollama/ollama/ WORKDIR /go/src/github.com/ollama/ollama/llm/generate ARG CGO_CFLAGS ENV GOARCH arm64 +RUN OLLAMA_SKIP_STATIC_GENERATE=1 OLLAMA_SKIP_CPU_GENERATE=1 bash gen_linux.sh + +FROM --platform=linux/arm64 nvcr.io/nvidia/l4t-jetpack:${JETPACK_6} AS cuda-build-jetpack6-arm64 +ARG CMAKE_VERSION +RUN apt-get update && apt-get install -y git curl && \ + curl -s -L https://github.com/Kitware/CMake/releases/download/v${CMAKE_VERSION}/cmake-${CMAKE_VERSION}-linux-$(uname -m).tar.gz | tar -zx -C /usr --strip-components 1 +COPY --from=llm-code / /go/src/github.com/ollama/ollama/ +WORKDIR /go/src/github.com/ollama/ollama/llm/generate +ARG CGO_CFLAGS +ENV GOARCH arm64 +ENV LIBRARY_PATH /usr/local/cuda/lib64/stubs RUN --mount=type=cache,target=/root/.ccache \ OLLAMA_SKIP_STATIC_GENERATE=1 \ OLLAMA_SKIP_CPU_GENERATE=1 \ - CMAKE_CUDA_ARCHITECTURES="${CUDA_V11_ARCHITECTURES}" \ - CUDA_VARIANT="_v11" \ + CUDA_VARIANT="_jetpack6" \ + CUDA_DIST_DIR="/go/src/github.com/ollama/ollama/dist/linux-arm64/ollama_libs/cuda_jetpack6" \ + CMAKE_CUDA_ARCHITECTURES="87" \ + bash gen_linux.sh + +FROM --platform=linux/arm64 nvcr.io/nvidia/l4t-jetpack:${JETPACK_5} AS cuda-build-jetpack5-arm64 +ARG CMAKE_VERSION +RUN apt-get update && apt-get install -y git curl && \ + curl -s -L https://github.com/Kitware/CMake/releases/download/v${CMAKE_VERSION}/cmake-${CMAKE_VERSION}-linux-$(uname -m).tar.gz | tar -zx -C /usr --strip-components 1 +COPY --from=llm-code / /go/src/github.com/ollama/ollama/ +WORKDIR /go/src/github.com/ollama/ollama/llm/generate +ARG CGO_CFLAGS +ENV GOARCH arm64 +ENV LIBRARY_PATH /usr/local/cuda/lib64/stubs +RUN --mount=type=cache,target=/root/.ccache \ + OLLAMA_SKIP_STATIC_GENERATE=1 \ + OLLAMA_SKIP_CPU_GENERATE=1 \ + CUDA_VARIANT="_jetpack5" \ + CUDA_DIST_DIR="/go/src/github.com/ollama/ollama/dist/linux-arm64/ollama_libs/cuda_jetpack5" \ + CMAKE_CUDA_ARCHITECTURES="72;87" \ bash gen_linux.sh FROM --platform=linux/amd64 rocm/dev-centos-7:${ROCM_VERSION}-complete AS rocm-build-amd64 @@ -123,8 +155,14 @@ ARG GOLANG_VERSION WORKDIR /go/src/github.com/ollama/ollama COPY . . COPY --from=static-build-arm64 /go/src/github.com/ollama/ollama/llm/build/linux/ llm/build/linux/ -COPY --from=cuda-build-arm64 /go/src/github.com/ollama/ollama/dist/ dist/ -COPY --from=cuda-build-arm64 /go/src/github.com/ollama/ollama/llm/build/linux/ llm/build/linux/ +COPY --from=cuda-build-server-arm64 /go/src/github.com/ollama/ollama/dist/ dist/ +COPY --from=cuda-build-server-arm64 /go/src/github.com/ollama/ollama/llm/build/linux/ llm/build/linux/ +## arm binary += 381M +COPY --from=cuda-build-jetpack6-arm64 /go/src/github.com/ollama/ollama/llm/build/linux/ llm/build/linux/ +COPY --from=cuda-build-jetpack6-arm64 /go/src/github.com/ollama/ollama/dist/ dist/ +## arm binary += 330M +COPY --from=cuda-build-jetpack5-arm64 /go/src/github.com/ollama/ollama/llm/build/linux/ llm/build/linux/ +COPY --from=cuda-build-jetpack5-arm64 /go/src/github.com/ollama/ollama/dist/ dist/ ARG GOFLAGS ARG CGO_CFLAGS RUN --mount=type=cache,target=/root/.ccache \ diff --git a/gpu/gpu.go b/gpu/gpu.go index d0ae0f34f..22461922d 100644 --- a/gpu/gpu.go +++ b/gpu/gpu.go @@ -15,7 +15,9 @@ import ( "log/slog" "os" "path/filepath" + "regexp" "runtime" + "strconv" "strings" "sync" "unsafe" @@ -215,7 +217,7 @@ func GetGPUInfo() GpuInfoList { GpuInfo: GpuInfo{ memInfo: mem, Library: "cpu", - Variant: cpuCapability, + Variant: cpuCapability.String(), ID: "0", }, }, @@ -231,6 +233,35 @@ func GetGPUInfo() GpuInfoList { depPath := GetDepDir() + var cudaVariant string + if runtime.GOARCH == "arm64" && runtime.GOOS == "linux" { + if CudaTegra != "" { + ver := strings.Split(CudaTegra, ".") + if len(ver) > 0 { + cudaVariant = "jetpack" + ver[0] + } + } else if data, err := os.ReadFile("/etc/nv_tegra_release"); err == nil { + r := regexp.MustCompile(` R(\d+) `) + m := r.FindSubmatch(data) + if len(m) != 2 { + slog.Info("Unexpected format for /etc/nv_tegra_release. Set JETSON_JETPACK to select version") + } else { + if l4t, err := strconv.Atoi(string(m[1])); err == nil { + // Note: mapping from L4t -> JP is inconsistent (can't just subtract 30) + // https://developer.nvidia.com/embedded/jetpack-archive + switch l4t { + case 35: + cudaVariant = "jetpack5" + case 36: + cudaVariant = "jetpack6" + default: + slog.Info("unsupported L4T version", "nv_tegra_release", string(data)) + } + } + } + } + } + // Load ALL libraries cHandles = initCudaHandles() @@ -240,6 +271,7 @@ func GetGPUInfo() GpuInfoList { gpuInfo := CudaGPUInfo{ GpuInfo: GpuInfo{ Library: "cuda", + Variant: cudaVariant, }, index: i, } @@ -266,7 +298,15 @@ func GetGPUInfo() GpuInfoList { gpuInfo.ID = C.GoString(&memInfo.gpu_id[0]) gpuInfo.Compute = fmt.Sprintf("%d.%d", memInfo.major, memInfo.minor) gpuInfo.MinimumMemory = cudaMinimumMemory - gpuInfo.DependencyPath = depPath + if depPath != "" { + gpuInfo.DependencyPath = depPath + // Check for variant specific directory + if cudaVariant != "" { + if _, err := os.Stat(filepath.Join(depPath, "cuda_"+cudaVariant)); err == nil { + gpuInfo.DependencyPath = filepath.Join(depPath, "cuda_"+cudaVariant) + } + } + } gpuInfo.Name = C.GoString(&memInfo.gpu_name[0]) gpuInfo.DriverMajor = driverMajor gpuInfo.DriverMinor = driverMinor diff --git a/gpu/gpu_darwin.go b/gpu/gpu_darwin.go index 9d9fd84eb..417b48dfa 100644 --- a/gpu/gpu_darwin.go +++ b/gpu/gpu_darwin.go @@ -25,7 +25,7 @@ func GetGPUInfo() GpuInfoList { return []GpuInfo{ { Library: "cpu", - Variant: GetCPUCapability(), + Variant: GetCPUCapability().String(), memInfo: mem, }, } @@ -48,7 +48,7 @@ func GetCPUInfo() GpuInfoList { return []GpuInfo{ { Library: "cpu", - Variant: GetCPUCapability(), + Variant: GetCPUCapability().String(), memInfo: mem, }, } diff --git a/gpu/types.go b/gpu/types.go index 8d22b06bf..fc628d471 100644 --- a/gpu/types.go +++ b/gpu/types.go @@ -19,7 +19,7 @@ type GpuInfo struct { Library string `json:"library,omitempty"` // Optional variant to select (e.g. versions, cpu feature flags) - Variant CPUCapability `json:"variant"` + Variant string `json:"variant"` // MinimumMemory represents the minimum memory required to use the GPU MinimumMemory uint64 `json:"-"` @@ -81,8 +81,8 @@ func (l GpuInfoList) ByLibrary() []GpuInfoList { for _, info := range l { found := false requested := info.Library - if info.Variant != CPUCapabilityNone { - requested += "_" + info.Variant.String() + if info.Variant != CPUCapabilityNone.String() { + requested += "_" + info.Variant } for i, lib := range libs { if lib == requested { diff --git a/llm/generate/gen_linux.sh b/llm/generate/gen_linux.sh index 1365d07db..dc9dda5a3 100755 --- a/llm/generate/gen_linux.sh +++ b/llm/generate/gen_linux.sh @@ -165,7 +165,7 @@ if [ -z "${OLLAMA_SKIP_CUDA_GENERATE}" -a -d "${CUDA_LIB_DIR}" ]; then echo "CUDA libraries detected - building dynamic CUDA library" init_vars CUDA_MAJOR=$(ls "${CUDA_LIB_DIR}"/libcudart.so.* | head -1 | cut -f3 -d. || true) - if [ -n "${CUDA_MAJOR}" ]; then + if [ -n "${CUDA_MAJOR}" -a -z "${CUDA_VARIANT}" ]; then CUDA_VARIANT=_v${CUDA_MAJOR} fi if [ "${ARCH}" == "arm64" ]; then @@ -189,9 +189,10 @@ if [ -z "${OLLAMA_SKIP_CUDA_GENERATE}" -a -d "${CUDA_LIB_DIR}" ]; then CMAKE_DEFS="${COMMON_CMAKE_DEFS} ${CMAKE_DEFS} ${ARM64_DEFS} ${CMAKE_CUDA_DEFS} -DGGML_STATIC=off" BUILD_DIR="../build/linux/${ARCH}/cuda${CUDA_VARIANT}" export LLAMA_SERVER_LDFLAGS="-L${CUDA_LIB_DIR} -lcudart -lcublas -lcublasLt -lcuda" - CUDA_DIST_DIR="${DIST_BASE}/ollama_libs" + CUDA_DIST_DIR="${CUDA_DIST_DIR:-${DIST_BASE}/ollama_libs}" build install + echo "Installing CUDA dependencies in ${CUDA_DIST_DIR}" mkdir -p "${CUDA_DIST_DIR}" for lib in ${CUDA_LIB_DIR}/libcudart.so* ${CUDA_LIB_DIR}/libcublas.so* ${CUDA_LIB_DIR}/libcublasLt.so* ; do cp -a "${lib}" "${CUDA_DIST_DIR}" diff --git a/llm/payload.go b/llm/payload.go index b402e1f24..963b32959 100644 --- a/llm/payload.go +++ b/llm/payload.go @@ -82,8 +82,8 @@ func serversForGpu(info gpu.GpuInfo) []string { // glob workDir for files that start with ollama_ availableServers := getAvailableServers() requested := info.Library - if info.Variant != gpu.CPUCapabilityNone { - requested += "_" + info.Variant.String() + if info.Variant != gpu.CPUCapabilityNone.String() { + requested += "_" + info.Variant } servers := []string{} diff --git a/scripts/build_linux.sh b/scripts/build_linux.sh index ebb60c5a7..adda2ad75 100755 --- a/scripts/build_linux.sh +++ b/scripts/build_linux.sh @@ -22,6 +22,7 @@ for TARGETARCH in ${BUILD_ARCH}; do -t builder:$TARGETARCH \ . docker create --platform linux/$TARGETARCH --name builder-$TARGETARCH builder:$TARGETARCH + rm -rf ./dist/linux-$TARGETARCH docker cp builder-$TARGETARCH:/go/src/github.com/ollama/ollama/dist/linux-$TARGETARCH ./dist docker rm builder-$TARGETARCH echo "Compressing final linux bundle..." From fc3b4cda89f468f923e2e6095c6a62a5c3c336ff Mon Sep 17 00:00:00 2001 From: Daniel Hiltgen Date: Wed, 19 Jun 2024 09:36:30 -0700 Subject: [PATCH 260/384] Report GPU variant in log --- gpu/types.go | 1 + 1 file changed, 1 insertion(+) diff --git a/gpu/types.go b/gpu/types.go index fc628d471..885390789 100644 --- a/gpu/types.go +++ b/gpu/types.go @@ -105,6 +105,7 @@ func (l GpuInfoList) LogDetails() { slog.Info("inference compute", "id", g.ID, "library", g.Library, + "variant", g.Variant, "compute", g.Compute, "driver", fmt.Sprintf("%d.%d", g.DriverMajor, g.DriverMinor), "name", g.Name, From 4fe3a556faf790ba993223cfdda16e281b6cb76d Mon Sep 17 00:00:00 2001 From: Daniel Hiltgen Date: Thu, 13 Jun 2024 20:46:14 -0700 Subject: [PATCH 261/384] Add cuda v12 variant and selection logic Based on compute capability and driver version, pick v12 or v11 cuda variants. --- Dockerfile | 43 +++++++++++++++++++++++++++++++++---------- gpu/cuda_common.go | 43 +++++++++++++++++++++++++++++++++++++++++++ gpu/gpu.go | 40 ++++------------------------------------ gpu/types.go | 6 ++++-- 4 files changed, 84 insertions(+), 48 deletions(-) diff --git a/Dockerfile b/Dockerfile index 79b2a6961..e200f5d4c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,7 +1,7 @@ ARG GOLANG_VERSION=1.22.5 ARG CMAKE_VERSION=3.22.1 -# this CUDA_VERSION corresponds with the one specified in docs/gpu.md -ARG CUDA_VERSION=11.3.1 +ARG CUDA_VERSION_11=11.3.1 +ARG CUDA_VERSION_12=12.4.0 ARG ROCM_VERSION=6.1.2 ARG JETPACK_6=r36.2.0 ARG JETPACK_5=r35.4.1 @@ -13,7 +13,7 @@ COPY .git .git COPY .gitmodules .gitmodules COPY llm llm -FROM --platform=linux/amd64 nvidia/cuda:$CUDA_VERSION-devel-centos7 AS cuda-build-amd64 +FROM --platform=linux/amd64 nvidia/cuda:$CUDA_VERSION_11-devel-centos7 AS cuda-11-build-amd64 ARG CMAKE_VERSION COPY ./scripts/rh_linux_deps.sh / RUN CMAKE_VERSION=${CMAKE_VERSION} sh /rh_linux_deps.sh @@ -23,9 +23,29 @@ WORKDIR /go/src/github.com/ollama/ollama/llm/generate ARG CGO_CFLAGS ENV GOARCH amd64 RUN --mount=type=cache,target=/root/.ccache \ - OLLAMA_SKIP_STATIC_GENERATE=1 OLLAMA_SKIP_CPU_GENERATE=1 bash gen_linux.sh + OLLAMA_SKIP_STATIC_GENERATE=1 \ + OLLAMA_SKIP_CPU_GENERATE=1 \ + CMAKE_CUDA_ARCHITECTURES="50;52;53;60;61;62;70;72;75;80;86" \ + CUDA_VARIANT="_v11" \ + bash gen_linux.sh -FROM --platform=linux/arm64 nvidia/cuda:$CUDA_VERSION-devel-rockylinux8 AS cuda-build-server-arm64 +FROM --platform=linux/amd64 nvidia/cuda:$CUDA_VERSION_12-devel-centos7 AS cuda-12-build-amd64 +ARG CMAKE_VERSION +COPY ./scripts/rh_linux_deps.sh / +RUN CMAKE_VERSION=${CMAKE_VERSION} sh /rh_linux_deps.sh +ENV PATH /opt/rh/devtoolset-10/root/usr/bin:$PATH +COPY --from=llm-code / /go/src/github.com/ollama/ollama/ +WORKDIR /go/src/github.com/ollama/ollama/llm/generate +ARG CGO_CFLAGS +ENV GOARCH amd64 +RUN --mount=type=cache,target=/root/.ccache \ + OLLAMA_SKIP_STATIC_GENERATE=1 \ + OLLAMA_SKIP_CPU_GENERATE=1 \ + CMAKE_CUDA_ARCHITECTURES="60;61;62;70;72;75;80;86;87;89;90;90a" \ + CUDA_VARIANT="_v12" \ + bash gen_linux.sh + +FROM --platform=linux/arm64 nvidia/cuda:$CUDA_VERSION_11-devel-rockylinux8 AS cuda-11-build-server-arm64 ARG CMAKE_VERSION COPY ./scripts/rh_linux_deps.sh / RUN CMAKE_VERSION=${CMAKE_VERSION} sh /rh_linux_deps.sh @@ -34,7 +54,8 @@ COPY --from=llm-code / /go/src/github.com/ollama/ollama/ WORKDIR /go/src/github.com/ollama/ollama/llm/generate ARG CGO_CFLAGS ENV GOARCH arm64 -RUN OLLAMA_SKIP_STATIC_GENERATE=1 OLLAMA_SKIP_CPU_GENERATE=1 bash gen_linux.sh +RUN --mount=type=cache,target=/root/.ccache \ + OLLAMA_SKIP_STATIC_GENERATE=1 OLLAMA_SKIP_CPU_GENERATE=1 bash gen_linux.sh FROM --platform=linux/arm64 nvcr.io/nvidia/l4t-jetpack:${JETPACK_6} AS cuda-build-jetpack6-arm64 ARG CMAKE_VERSION @@ -139,8 +160,10 @@ COPY . . COPY --from=static-build-amd64 /go/src/github.com/ollama/ollama/llm/build/linux/ llm/build/linux/ COPY --from=cpu_avx-build-amd64 /go/src/github.com/ollama/ollama/llm/build/linux/ llm/build/linux/ COPY --from=cpu_avx2-build-amd64 /go/src/github.com/ollama/ollama/llm/build/linux/ llm/build/linux/ -COPY --from=cuda-build-amd64 /go/src/github.com/ollama/ollama/dist/ dist/ -COPY --from=cuda-build-amd64 /go/src/github.com/ollama/ollama/llm/build/linux/ llm/build/linux/ +COPY --from=cuda-11-build-amd64 /go/src/github.com/ollama/ollama/dist/ dist/ +COPY --from=cuda-11-build-amd64 /go/src/github.com/ollama/ollama/llm/build/linux/ llm/build/linux/ +COPY --from=cuda-12-build-amd64 /go/src/github.com/ollama/ollama/dist/ dist/ +COPY --from=cuda-12-build-amd64 /go/src/github.com/ollama/ollama/llm/build/linux/ llm/build/linux/ COPY --from=rocm-build-amd64 /go/src/github.com/ollama/ollama/dist/ dist/ COPY --from=rocm-build-amd64 /go/src/github.com/ollama/ollama/llm/build/linux/ llm/build/linux/ ARG GOFLAGS @@ -155,8 +178,8 @@ ARG GOLANG_VERSION WORKDIR /go/src/github.com/ollama/ollama COPY . . COPY --from=static-build-arm64 /go/src/github.com/ollama/ollama/llm/build/linux/ llm/build/linux/ -COPY --from=cuda-build-server-arm64 /go/src/github.com/ollama/ollama/dist/ dist/ -COPY --from=cuda-build-server-arm64 /go/src/github.com/ollama/ollama/llm/build/linux/ llm/build/linux/ +COPY --from=cuda-11-build-server-arm64 /go/src/github.com/ollama/ollama/dist/ dist/ +COPY --from=cuda-11-build-server-arm64 /go/src/github.com/ollama/ollama/llm/build/linux/ llm/build/linux/ ## arm binary += 381M COPY --from=cuda-build-jetpack6-arm64 /go/src/github.com/ollama/ollama/llm/build/linux/ llm/build/linux/ COPY --from=cuda-build-jetpack6-arm64 /go/src/github.com/ollama/ollama/dist/ dist/ diff --git a/gpu/cuda_common.go b/gpu/cuda_common.go index c90a644c4..defaa60a3 100644 --- a/gpu/cuda_common.go +++ b/gpu/cuda_common.go @@ -4,9 +4,17 @@ package gpu import ( "log/slog" + "os" + "regexp" + "runtime" + "strconv" "strings" ) +// Jetson devices have JETSON_JETPACK="x.y.z" factory set to the Jetpack version installed. +// Included to drive logic for reducing Ollama-allocated overhead on L4T/Jetson devices. +var CudaTegra string = os.Getenv("JETSON_JETPACK") + func cudaGetVisibleDevicesEnv(gpuInfo []GpuInfo) (string, string) { ids := []string{} for _, info := range gpuInfo { @@ -19,3 +27,38 @@ func cudaGetVisibleDevicesEnv(gpuInfo []GpuInfo) (string, string) { } return "CUDA_VISIBLE_DEVICES", strings.Join(ids, ",") } + +func cudaGetVariant(gpuInfo CudaGPUInfo) string { + if runtime.GOARCH == "arm64" && runtime.GOOS == "linux" { + if CudaTegra != "" { + ver := strings.Split(CudaTegra, ".") + if len(ver) > 0 { + return "jetpack" + ver[0] + } + } else if data, err := os.ReadFile("/etc/nv_tegra_release"); err == nil { + r := regexp.MustCompile(` R(\d+) `) + m := r.FindSubmatch(data) + if len(m) != 2 { + slog.Info("Unexpected format for /etc/nv_tegra_release. Set JETSON_JETPACK to select version") + } else { + if l4t, err := strconv.Atoi(string(m[1])); err == nil { + // Note: mapping from L4t -> JP is inconsistent (can't just subtract 30) + // https://developer.nvidia.com/embedded/jetpack-archive + switch l4t { + case 35: + return "jetpack5" + case 36: + return "jetpack6" + default: + slog.Info("unsupported L4T version", "nv_tegra_release", string(data)) + } + } + } + } + } + + if gpuInfo.computeMajor < 6 || gpuInfo.DriverMajor < 12 { + return "v11" + } + return "v12" +} diff --git a/gpu/gpu.go b/gpu/gpu.go index 22461922d..eb87807a7 100644 --- a/gpu/gpu.go +++ b/gpu/gpu.go @@ -15,9 +15,7 @@ import ( "log/slog" "os" "path/filepath" - "regexp" "runtime" - "strconv" "strings" "sync" "unsafe" @@ -66,10 +64,6 @@ var RocmComputeMin = 9 // TODO find a better way to detect iGPU instead of minimum memory const IGPUMemLimit = 1 * format.GibiByte // 512G is what they typically report, so anything less than 1G must be iGPU -// Jetson devices have JETSON_JETPACK="x.y.z" factory set to the Jetpack version installed. -// Included to drive logic for reducing Ollama-allocated overhead on L4T/Jetson devices. -var CudaTegra string = os.Getenv("JETSON_JETPACK") - // Note: gpuMutex must already be held func initCudaHandles() *cudaHandles { // TODO - if the ollama build is CPU only, don't do these checks as they're irrelevant and confusing @@ -233,35 +227,6 @@ func GetGPUInfo() GpuInfoList { depPath := GetDepDir() - var cudaVariant string - if runtime.GOARCH == "arm64" && runtime.GOOS == "linux" { - if CudaTegra != "" { - ver := strings.Split(CudaTegra, ".") - if len(ver) > 0 { - cudaVariant = "jetpack" + ver[0] - } - } else if data, err := os.ReadFile("/etc/nv_tegra_release"); err == nil { - r := regexp.MustCompile(` R(\d+) `) - m := r.FindSubmatch(data) - if len(m) != 2 { - slog.Info("Unexpected format for /etc/nv_tegra_release. Set JETSON_JETPACK to select version") - } else { - if l4t, err := strconv.Atoi(string(m[1])); err == nil { - // Note: mapping from L4t -> JP is inconsistent (can't just subtract 30) - // https://developer.nvidia.com/embedded/jetpack-archive - switch l4t { - case 35: - cudaVariant = "jetpack5" - case 36: - cudaVariant = "jetpack6" - default: - slog.Info("unsupported L4T version", "nv_tegra_release", string(data)) - } - } - } - } - } - // Load ALL libraries cHandles = initCudaHandles() @@ -271,7 +236,6 @@ func GetGPUInfo() GpuInfoList { gpuInfo := CudaGPUInfo{ GpuInfo: GpuInfo{ Library: "cuda", - Variant: cudaVariant, }, index: i, } @@ -297,7 +261,10 @@ func GetGPUInfo() GpuInfoList { gpuInfo.FreeMemory = uint64(memInfo.free) gpuInfo.ID = C.GoString(&memInfo.gpu_id[0]) gpuInfo.Compute = fmt.Sprintf("%d.%d", memInfo.major, memInfo.minor) + gpuInfo.computeMajor = int(memInfo.major) + gpuInfo.computeMinor = int(memInfo.minor) gpuInfo.MinimumMemory = cudaMinimumMemory + cudaVariant := cudaGetVariant(gpuInfo) if depPath != "" { gpuInfo.DependencyPath = depPath // Check for variant specific directory @@ -310,6 +277,7 @@ func GetGPUInfo() GpuInfoList { gpuInfo.Name = C.GoString(&memInfo.gpu_name[0]) gpuInfo.DriverMajor = driverMajor gpuInfo.DriverMinor = driverMinor + gpuInfo.Variant = cudaGetVariant(gpuInfo) // query the management library as well so we can record any skew between the two // which represents overhead on the GPU we must set aside on subsequent updates diff --git a/gpu/types.go b/gpu/types.go index 885390789..4cbbeb841 100644 --- a/gpu/types.go +++ b/gpu/types.go @@ -53,8 +53,10 @@ type CPUInfo struct { type CudaGPUInfo struct { GpuInfo - OSOverhead uint64 // Memory overhead between the driver library and management library - index int //nolint:unused,nolintlint + OSOverhead uint64 // Memory overhead between the driver library and management library + index int //nolint:unused,nolintlint + computeMajor int //nolint:unused,nolintlint + computeMinor int //nolint:unused,nolintlint } type CudaGPUInfoList []CudaGPUInfo From f6c811b32075cb3b7633d7d4213251d474a77682 Mon Sep 17 00:00:00 2001 From: Daniel Hiltgen Date: Fri, 12 Jul 2024 11:35:41 -0700 Subject: [PATCH 262/384] Enable cuda v12 flags --- Dockerfile | 35 ++++++++++++++++++++++++++++++++--- 1 file changed, 32 insertions(+), 3 deletions(-) diff --git a/Dockerfile b/Dockerfile index e200f5d4c..e83a266af 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,7 +1,9 @@ ARG GOLANG_VERSION=1.22.5 ARG CMAKE_VERSION=3.22.1 ARG CUDA_VERSION_11=11.3.1 +ARG CUDA_V11_ARCHITECTURES="50;52;53;60;61;62;70;72;75;80;86" ARG CUDA_VERSION_12=12.4.0 +ARG CUDA_V12_ARCHITECTURES="60;61;62;70;72;75;80;86;87;89;90;90a" ARG ROCM_VERSION=6.1.2 ARG JETPACK_6=r36.2.0 ARG JETPACK_5=r35.4.1 @@ -21,11 +23,12 @@ ENV PATH /opt/rh/devtoolset-10/root/usr/bin:$PATH COPY --from=llm-code / /go/src/github.com/ollama/ollama/ WORKDIR /go/src/github.com/ollama/ollama/llm/generate ARG CGO_CFLAGS +ARG CUDA_V11_ARCHITECTURES ENV GOARCH amd64 RUN --mount=type=cache,target=/root/.ccache \ OLLAMA_SKIP_STATIC_GENERATE=1 \ OLLAMA_SKIP_CPU_GENERATE=1 \ - CMAKE_CUDA_ARCHITECTURES="50;52;53;60;61;62;70;72;75;80;86" \ + CMAKE_CUDA_ARCHITECTURES="${CUDA_V11_ARCHITECTURES}" \ CUDA_VARIANT="_v11" \ bash gen_linux.sh @@ -37,12 +40,14 @@ ENV PATH /opt/rh/devtoolset-10/root/usr/bin:$PATH COPY --from=llm-code / /go/src/github.com/ollama/ollama/ WORKDIR /go/src/github.com/ollama/ollama/llm/generate ARG CGO_CFLAGS +ARG CUDA_V12_ARCHITECTURES ENV GOARCH amd64 RUN --mount=type=cache,target=/root/.ccache \ OLLAMA_SKIP_STATIC_GENERATE=1 \ OLLAMA_SKIP_CPU_GENERATE=1 \ - CMAKE_CUDA_ARCHITECTURES="60;61;62;70;72;75;80;86;87;89;90;90a" \ + CMAKE_CUDA_ARCHITECTURES="${CUDA_V12_ARCHITECTURES}" \ CUDA_VARIANT="_v12" \ + OLLAMA_CUSTOM_CUDA_DEFS="-DGGML_CUDA_USE_GRAPHS=on" \ bash gen_linux.sh FROM --platform=linux/arm64 nvidia/cuda:$CUDA_VERSION_11-devel-rockylinux8 AS cuda-11-build-server-arm64 @@ -53,9 +58,31 @@ ENV PATH /opt/rh/gcc-toolset-10/root/usr/bin:$PATH COPY --from=llm-code / /go/src/github.com/ollama/ollama/ WORKDIR /go/src/github.com/ollama/ollama/llm/generate ARG CGO_CFLAGS +ARG CUDA_V11_ARCHITECTURES +ENV GOARCH arm64 +RUN OLLAMA_SKIP_STATIC_GENERATE=1 \ + OLLAMA_SKIP_CPU_GENERATE=1 \ + CMAKE_CUDA_ARCHITECTURES="${CUDA_V11_ARCHITECTURES}" \ + CUDA_VARIANT="_v11" \ + bash gen_linux.sh + +FROM --platform=linux/arm64 nvidia/cuda:$CUDA_VERSION_12-devel-rockylinux8 AS cuda-12-build-server-arm64 +ARG CMAKE_VERSION +COPY ./scripts/rh_linux_deps.sh / +RUN CMAKE_VERSION=${CMAKE_VERSION} sh /rh_linux_deps.sh +ENV PATH /opt/rh/gcc-toolset-10/root/usr/bin:$PATH +COPY --from=llm-code / /go/src/github.com/ollama/ollama/ +WORKDIR /go/src/github.com/ollama/ollama/llm/generate +ARG CGO_CFLAGS +ARG CUDA_V12_ARCHITECTURES ENV GOARCH arm64 RUN --mount=type=cache,target=/root/.ccache \ - OLLAMA_SKIP_STATIC_GENERATE=1 OLLAMA_SKIP_CPU_GENERATE=1 bash gen_linux.sh + OLLAMA_SKIP_STATIC_GENERATE=1 \ + OLLAMA_SKIP_CPU_GENERATE=1 \ + CMAKE_CUDA_ARCHITECTURES="${CUDA_V12_ARCHITECTURES}" \ + CUDA_VARIANT="_v12" \ + OLLAMA_CUSTOM_CUDA_DEFS="-DGGML_CUDA_USE_GRAPHS=on" \ + bash gen_linux.sh FROM --platform=linux/arm64 nvcr.io/nvidia/l4t-jetpack:${JETPACK_6} AS cuda-build-jetpack6-arm64 ARG CMAKE_VERSION @@ -180,6 +207,8 @@ COPY . . COPY --from=static-build-arm64 /go/src/github.com/ollama/ollama/llm/build/linux/ llm/build/linux/ COPY --from=cuda-11-build-server-arm64 /go/src/github.com/ollama/ollama/dist/ dist/ COPY --from=cuda-11-build-server-arm64 /go/src/github.com/ollama/ollama/llm/build/linux/ llm/build/linux/ +COPY --from=cuda-12-build-server-arm64 /go/src/github.com/ollama/ollama/dist/ dist/ +COPY --from=cuda-12-build-server-arm64 /go/src/github.com/ollama/ollama/llm/build/linux/ llm/build/linux/ ## arm binary += 381M COPY --from=cuda-build-jetpack6-arm64 /go/src/github.com/ollama/ollama/llm/build/linux/ llm/build/linux/ COPY --from=cuda-build-jetpack6-arm64 /go/src/github.com/ollama/ollama/dist/ dist/ From 927d98a6cde43ffee3ef269cf013df5e96cbe767 Mon Sep 17 00:00:00 2001 From: Daniel Hiltgen Date: Fri, 12 Jul 2024 14:33:13 -0700 Subject: [PATCH 263/384] Add windows cuda v12 + v11 support --- .github/workflows/release.yaml | 93 ++++++++++++++++++++++++++++++++-- llm/generate/gen_windows.ps1 | 6 +-- scripts/build_windows.ps1 | 63 ++++++++++++++++++----- 3 files changed, 142 insertions(+), 20 deletions(-) diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 9287f6f75..4bd684554 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -183,8 +183,8 @@ jobs: name: windows-rocm-deps path: dist/deps/* - # CUDA generation step - generate-windows-cuda: + # CUDA v11 generation step + generate-windows-cuda-v11: environment: release runs-on: windows env: @@ -256,7 +256,89 @@ jobs: cp "${NVIDIA_DIR}\cublasLt64_*.dll" "dist\deps\" - uses: actions/upload-artifact@v4 with: - name: generate-windows-cuda + name: generate-windows-cuda-v11 + path: | + llm/build/**/bin/* + dist/windows-amd64/** + - uses: actions/upload-artifact@v4 + with: + name: windows-cuda-deps + path: dist/deps/* + + # CUDA v12 generation step + generate-windows-cuda-v12: + environment: release + runs-on: windows + env: + KEY_CONTAINER: ${{ vars.KEY_CONTAINER }} + steps: + - uses: actions/checkout@v4 + - name: Set Version + shell: bash + run: echo "VERSION=${GITHUB_REF_NAME#v}" >> $GITHUB_ENV + - uses: 'google-github-actions/auth@v2' + with: + project_id: 'ollama' + credentials_json: '${{ secrets.GOOGLE_SIGNING_CREDENTIALS }}' + - run: echo "${{ vars.OLLAMA_CERT }}" > ollama_inc.crt + - name: install Windows SDK 8.1 to get signtool + run: | + $ErrorActionPreference = "Stop" + write-host "downloading SDK" + Invoke-WebRequest -Uri "https://go.microsoft.com/fwlink/p/?LinkId=323507" -OutFile "${env:RUNNER_TEMP}\sdksetup.exe" + Start-Process "${env:RUNNER_TEMP}\sdksetup.exe" -ArgumentList @("/q") -NoNewWindow -Wait + write-host "Win SDK 8.1 installed" + gci -path 'C:\Program Files (x86)\Windows Kits\' -r -fi 'signtool.exe' + - name: install signing plugin + run: | + $ErrorActionPreference = "Stop" + write-host "downloading plugin" + Invoke-WebRequest -Uri "https://github.com/GoogleCloudPlatform/kms-integrations/releases/download/cng-v1.0/kmscng-1.0-windows-amd64.zip" -OutFile "${env:RUNNER_TEMP}\plugin.zip" + Expand-Archive -Path "${env:RUNNER_TEMP}\plugin.zip" -DestinationPath ${env:RUNNER_TEMP}\plugin\ + write-host "Installing plugin" + & "${env:RUNNER_TEMP}\plugin\*\kmscng.msi" /quiet + write-host "plugin installed" + - uses: actions/setup-go@v5 + with: + go-version-file: go.mod + cache: true + - name: 'Install CUDA' + run: | + $ErrorActionPreference = "Stop" + write-host "downloading CUDA Installer" + Invoke-WebRequest -Uri "https://developer.download.nvidia.com/compute/cuda/12.4.0/local_installers/cuda_12.4.0_551.61_windows.exe" -OutFile "${env:RUNNER_TEMP}\cuda-install.exe" + write-host "Installing CUDA" + Start-Process "${env:RUNNER_TEMP}\cuda-install.exe" -ArgumentList '-s' -NoNewWindow -Wait + write-host "Completed CUDA" + $cudaPath=((resolve-path "c:\Program Files\NVIDIA*\CUDA\v*\bin\nvcc.exe")[0].path | split-path | split-path) + $cudaVer=($cudaPath | split-path -leaf ) -replace 'v(\d+).(\d+)', '$1_$2' + echo "$cudaPath\bin" >> $env:GITHUB_PATH + echo "CUDA_PATH=$cudaPath" >> $env:GITHUB_ENV + echo "CUDA_PATH_V${cudaVer}=$cudaPath" >> $env:GITHUB_ENV + echo "CUDA_PATH_VX_Y=CUDA_PATH_V${cudaVer}" >> $env:GITHUB_ENV + - name: 'Verify CUDA' + run: nvcc -V + - run: go get ./... + - name: go generate + run: | + $gopath=(get-command go).source | split-path -parent + $cudabin=(get-command nvcc).source | split-path + & "C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\Tools\Launch-VsDevShell.ps1" + cd $env:GITHUB_WORKSPACE + $env:CMAKE_SYSTEM_VERSION="10.0.22621.0" + $env:PATH="$gopath;$cudabin;$env:PATH" + $env:OLLAMA_SKIP_CPU_GENERATE="1" + go generate -x ./... + - name: 'gather cuda dependencies' + run: | + $NVIDIA_DIR=(resolve-path 'C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\*\bin\')[0] + md "dist\deps" + cp "${NVIDIA_DIR}\cudart64_*.dll" "dist\deps\" + cp "${NVIDIA_DIR}\cublas64_*.dll" "dist\deps\" + cp "${NVIDIA_DIR}\cublasLt64_*.dll" "dist\deps\" + - uses: actions/upload-artifact@v4 + with: + name: generate-windows-cuda-v12 path: | llm/build/**/bin/* dist/windows-amd64/** @@ -270,7 +352,8 @@ jobs: environment: release runs-on: windows needs: - - generate-windows-cuda + - generate-windows-cuda-v11 + - generate-windows-cuda-v12 - generate-windows-rocm - generate-windows-cpu env: @@ -314,7 +397,7 @@ jobs: name: generate-windows-cpu - uses: actions/download-artifact@v4 with: - name: generate-windows-cuda + name: generate-windows-cuda-v11 - uses: actions/download-artifact@v4 with: name: windows-cuda-deps diff --git a/llm/generate/gen_windows.ps1 b/llm/generate/gen_windows.ps1 index 1f8c96d8f..42708d3e0 100644 --- a/llm/generate/gen_windows.ps1 +++ b/llm/generate/gen_windows.ps1 @@ -261,7 +261,7 @@ function build_cuda() { if ((-not "${env:OLLAMA_SKIP_CUDA_GENERATE}") -and ("${script:CUDA_LIB_DIR}")) { # Then build cuda as a dynamically loaded library $nvcc = "$script:CUDA_LIB_DIR\nvcc.exe" - $script:CUDA_VERSION=(get-item ($nvcc | split-path | split-path)).Basename + $script:CUDA_VERSION=((get-item ($nvcc | split-path | split-path)).Basename -Split "\.")[0] if ($null -ne $script:CUDA_VERSION) { $script:CUDA_VARIANT="_"+$script:CUDA_VERSION } @@ -273,9 +273,9 @@ function build_cuda() { "-DGGML_CUDA=ON", "-DGGML_AVX=on", "-DGGML_AVX2=off", - "-DCUDAToolkit_INCLUDE_DIR=$script:CUDA_INCLUDE_DIR", "-DCMAKE_CUDA_FLAGS=-t8", - "-DCMAKE_CUDA_ARCHITECTURES=${script:CMAKE_CUDA_ARCHITECTURES}" + "-DCMAKE_CUDA_ARCHITECTURES=${script:CMAKE_CUDA_ARCHITECTURES}", + "-DCMAKE_CUDA_COMPILER_TOOLKIT_ROOT=$env:CUDA_PATH" ) if ($null -ne $env:OLLAMA_CUSTOM_CUDA_DEFS) { write-host "OLLAMA_CUSTOM_CUDA_DEFS=`"${env:OLLAMA_CUSTOM_CUDA_DEFS}`"" diff --git a/scripts/build_windows.ps1 b/scripts/build_windows.ps1 index e8d851f4f..50b602305 100644 --- a/scripts/build_windows.ps1 +++ b/scripts/build_windows.ps1 @@ -7,6 +7,7 @@ $ErrorActionPreference = "Stop" function checkEnv() { + $script:ARCH = $Env:PROCESSOR_ARCHITECTURE.ToLower() $script:TARGET_ARCH=$Env:PROCESSOR_ARCHITECTURE.ToLower() Write-host "Building for ${script:TARGET_ARCH}" write-host "Locating required tools and paths" @@ -15,26 +16,23 @@ function checkEnv() { $MSVC_INSTALL=(Get-CimInstance MSFT_VSInstance -Namespace root/cimv2/vs)[0].InstallLocation $env:VCToolsRedistDir=(get-item "${MSVC_INSTALL}\VC\Redist\MSVC\*")[0] } - # Try to find the CUDA dir - if ($null -eq $env:NVIDIA_DIR) { + # Locate CUDA versions + # Note: this assumes every version found will be built + $cudaList=(get-item "C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v*\bin\" -ea 'silentlycontinue') + if ($cudaList.length -eq 0) { $d=(get-command -ea 'silentlycontinue' nvcc).path - if ($d -ne $null) { - $script:NVIDIA_DIR=($d| split-path -parent) - } else { - $cudaList=(get-item "C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v*\bin\" -ea 'silentlycontinue') - if ($cudaList.length > 0) { - $script:NVIDIA_DIR=$cudaList[0] - } + if ($null -ne $d) { + $script:CUDA_DIRS=@($d| split-path -parent) } } else { - $script:NVIDIA_DIR=$env:NVIDIA_DIR + $script:CUDA_DIRS=$cudaList } $script:INNO_SETUP_DIR=(get-item "C:\Program Files*\Inno Setup*\")[0] $script:DEPS_DIR="${script:SRC_DIR}\dist\windows-${script:TARGET_ARCH}" $env:CGO_ENABLED="1" - echo "Checking version" + Write-Output "Checking version" if (!$env:VERSION) { $data=(git describe --tags --first-parent --abbrev=7 --long --dirty --always) $pattern="v(.+)" @@ -71,7 +69,48 @@ function checkEnv() { function buildOllama() { write-host "Building ollama CLI" if ($null -eq ${env:OLLAMA_SKIP_GENERATE}) { - & go generate ./... + Remove-Item -ea 0 -recurse -force -path "${script:SRC_DIR}\dist\windows-${script:ARCH}" + + # TODO - consider trying to parallelize this with Start-ThreadJob, but env vars can't be used to toggle + # which targets to build + + # Start by skipping CUDA to build everything else + pwsh -Command { $env:OLLAMA_SKIP_CUDA_GENERATE="1"; & go generate ./... } + if ($LASTEXITCODE -ne 0) { exit($LASTEXITCODE)} + + # Then skip everyhting else and build all the CUDA variants + foreach ($env:CUDA_LIB_DIR in $script:CUDA_DIRS) { + write-host "Building CUDA ${env:CUDA_LIB_DIR}" + + if ($env:CUDA_LIB_DIR.Contains("v12")) { + pwsh -Command { + $env:OLLAMA_SKIP_CUDA_GENERATE="" + $env:OLLAMA_SKIP_STATIC_GENERATE="1" + $env:OLLAMA_SKIP_CPU_GENERATE="1" + $env:OLLAMA_SKIP_ONEAPI_GENERATE="1" + $env:OLLAMA_SKIP_ROCM_GENERATE="1" + $env:CMAKE_CUDA_ARCHITECTURES="60;61;62;70;72;75;80;86;87;89;90;90a" + $env:OLLAMA_CUSTOM_CUDA_DEFS="-DGGML_CUDA_USE_GRAPHS=on" + $env:CUDA_PATH=split-path -path $env:CUDA_LIB_DIR -parent + $env:PATH="$envs:CUDA_LIB_DIR;$env:PATH" + & go generate ./... + } + } else { + pwsh -Command { + $env:OLLAMA_SKIP_CUDA_GENERATE="" + $env:OLLAMA_SKIP_STATIC_GENERATE="1" + $env:OLLAMA_SKIP_CPU_GENERATE="1" + $env:OLLAMA_SKIP_ONEAPI_GENERATE="1" + $env:OLLAMA_SKIP_ROCM_GENERATE="1" + $env:CMAKE_CUDA_ARCHITECTURES="50;52;53;60;61;62;70;72;75;80;86" + $env:OLLAMA_CUSTOM_CUDA_DEFS="" + $env:CUDA_PATH=split-path -path $env:CUDA_LIB_DIR -parent + $env:PATH="$envs:CUDA_LIB_DIR;$env:PATH" + & go generate ./... + } + } + if ($LASTEXITCODE -ne 0) { exit($LASTEXITCODE)} + } if ($LASTEXITCODE -ne 0) { exit($LASTEXITCODE)} } else { write-host "Skipping generate step with OLLAMA_SKIP_GENERATE set" From 3b19cdba2a090772b2e886dbfbf712992fafe0cd Mon Sep 17 00:00:00 2001 From: Daniel Hiltgen Date: Tue, 13 Aug 2024 13:30:28 -0700 Subject: [PATCH 264/384] Remove Jetpack --- Dockerfile | 42 ------------------------------------------ 1 file changed, 42 deletions(-) diff --git a/Dockerfile b/Dockerfile index e83a266af..99ba5b65c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -5,9 +5,6 @@ ARG CUDA_V11_ARCHITECTURES="50;52;53;60;61;62;70;72;75;80;86" ARG CUDA_VERSION_12=12.4.0 ARG CUDA_V12_ARCHITECTURES="60;61;62;70;72;75;80;86;87;89;90;90a" ARG ROCM_VERSION=6.1.2 -ARG JETPACK_6=r36.2.0 -ARG JETPACK_5=r35.4.1 -ARG JETPACK_4=r32.7.1 # Copy the minimal context we need to run the generate scripts FROM scratch AS llm-code @@ -84,39 +81,6 @@ RUN --mount=type=cache,target=/root/.ccache \ OLLAMA_CUSTOM_CUDA_DEFS="-DGGML_CUDA_USE_GRAPHS=on" \ bash gen_linux.sh -FROM --platform=linux/arm64 nvcr.io/nvidia/l4t-jetpack:${JETPACK_6} AS cuda-build-jetpack6-arm64 -ARG CMAKE_VERSION -RUN apt-get update && apt-get install -y git curl && \ - curl -s -L https://github.com/Kitware/CMake/releases/download/v${CMAKE_VERSION}/cmake-${CMAKE_VERSION}-linux-$(uname -m).tar.gz | tar -zx -C /usr --strip-components 1 -COPY --from=llm-code / /go/src/github.com/ollama/ollama/ -WORKDIR /go/src/github.com/ollama/ollama/llm/generate -ARG CGO_CFLAGS -ENV GOARCH arm64 -ENV LIBRARY_PATH /usr/local/cuda/lib64/stubs -RUN --mount=type=cache,target=/root/.ccache \ - OLLAMA_SKIP_STATIC_GENERATE=1 \ - OLLAMA_SKIP_CPU_GENERATE=1 \ - CUDA_VARIANT="_jetpack6" \ - CUDA_DIST_DIR="/go/src/github.com/ollama/ollama/dist/linux-arm64/ollama_libs/cuda_jetpack6" \ - CMAKE_CUDA_ARCHITECTURES="87" \ - bash gen_linux.sh - -FROM --platform=linux/arm64 nvcr.io/nvidia/l4t-jetpack:${JETPACK_5} AS cuda-build-jetpack5-arm64 -ARG CMAKE_VERSION -RUN apt-get update && apt-get install -y git curl && \ - curl -s -L https://github.com/Kitware/CMake/releases/download/v${CMAKE_VERSION}/cmake-${CMAKE_VERSION}-linux-$(uname -m).tar.gz | tar -zx -C /usr --strip-components 1 -COPY --from=llm-code / /go/src/github.com/ollama/ollama/ -WORKDIR /go/src/github.com/ollama/ollama/llm/generate -ARG CGO_CFLAGS -ENV GOARCH arm64 -ENV LIBRARY_PATH /usr/local/cuda/lib64/stubs -RUN --mount=type=cache,target=/root/.ccache \ - OLLAMA_SKIP_STATIC_GENERATE=1 \ - OLLAMA_SKIP_CPU_GENERATE=1 \ - CUDA_VARIANT="_jetpack5" \ - CUDA_DIST_DIR="/go/src/github.com/ollama/ollama/dist/linux-arm64/ollama_libs/cuda_jetpack5" \ - CMAKE_CUDA_ARCHITECTURES="72;87" \ - bash gen_linux.sh FROM --platform=linux/amd64 rocm/dev-centos-7:${ROCM_VERSION}-complete AS rocm-build-amd64 ARG CMAKE_VERSION @@ -209,12 +173,6 @@ COPY --from=cuda-11-build-server-arm64 /go/src/github.com/ollama/ollama/dist/ di COPY --from=cuda-11-build-server-arm64 /go/src/github.com/ollama/ollama/llm/build/linux/ llm/build/linux/ COPY --from=cuda-12-build-server-arm64 /go/src/github.com/ollama/ollama/dist/ dist/ COPY --from=cuda-12-build-server-arm64 /go/src/github.com/ollama/ollama/llm/build/linux/ llm/build/linux/ -## arm binary += 381M -COPY --from=cuda-build-jetpack6-arm64 /go/src/github.com/ollama/ollama/llm/build/linux/ llm/build/linux/ -COPY --from=cuda-build-jetpack6-arm64 /go/src/github.com/ollama/ollama/dist/ dist/ -## arm binary += 330M -COPY --from=cuda-build-jetpack5-arm64 /go/src/github.com/ollama/ollama/llm/build/linux/ llm/build/linux/ -COPY --from=cuda-build-jetpack5-arm64 /go/src/github.com/ollama/ollama/dist/ dist/ ARG GOFLAGS ARG CGO_CFLAGS RUN --mount=type=cache,target=/root/.ccache \ From 88bb9e332877dfbba40030c19570fdbe00f41a21 Mon Sep 17 00:00:00 2001 From: Daniel Hiltgen Date: Wed, 14 Aug 2024 16:32:57 -0700 Subject: [PATCH 265/384] Adjust layout to bin+lib/ollama --- Dockerfile | 23 ++++++++++++++------ app/ollama.iss | 12 +++++------ docs/linux.md | 10 ++++----- envconfig/config.go | 6 +++--- gpu/amd_common.go | 2 +- gpu/amd_windows.go | 2 +- gpu/gpu.go | 4 ++-- llm/generate/gen_linux.sh | 6 +++--- llm/generate/gen_windows.ps1 | 42 ++++++++++++++++++------------------ scripts/build_windows.ps1 | 16 +++++++------- scripts/install.sh | 14 +++++++----- 11 files changed, 74 insertions(+), 63 deletions(-) diff --git a/Dockerfile b/Dockerfile index 99ba5b65c..d4b869180 100644 --- a/Dockerfile +++ b/Dockerfile @@ -95,8 +95,8 @@ ARG AMDGPU_TARGETS ENV GOARCH amd64 RUN --mount=type=cache,target=/root/.ccache \ OLLAMA_SKIP_STATIC_GENERATE=1 OLLAMA_SKIP_CPU_GENERATE=1 bash gen_linux.sh -RUN mkdir -p ../../dist/linux-amd64/ollama_libs && \ - (cd /opt/rocm/lib && tar cf - rocblas/library) | (cd ../../dist/linux-amd64/ollama_libs && tar xf - ) +RUN mkdir -p ../../dist/linux-amd64/lib/ollama && \ + (cd /opt/rocm/lib && tar cf - rocblas/library) | (cd ../../dist/linux-amd64/lib/ollama && tar xf - ) FROM --platform=linux/amd64 centos:7 AS cpu-builder-amd64 ARG CMAKE_VERSION @@ -160,7 +160,7 @@ COPY --from=rocm-build-amd64 /go/src/github.com/ollama/ollama/llm/build/linux/ l ARG GOFLAGS ARG CGO_CFLAGS RUN --mount=type=cache,target=/root/.ccache \ - go build -trimpath -o dist/linux-amd64/ollama . + go build -trimpath -o dist/linux-amd64/bin/ollama . # Intermediate stage used for ./scripts/build_linux.sh FROM --platform=linux/arm64 cpu-build-arm64 AS build-arm64 @@ -176,20 +176,29 @@ COPY --from=cuda-12-build-server-arm64 /go/src/github.com/ollama/ollama/llm/buil ARG GOFLAGS ARG CGO_CFLAGS RUN --mount=type=cache,target=/root/.ccache \ - go build -trimpath -o dist/linux-arm64/ollama . + go build -trimpath -o dist/linux-arm64/bin/ollama . + +# Strip out ROCm dependencies to keep the primary image lean +FROM --platform=linux/amd64 ubuntu:22.04 as amd64-libs-without-rocm +COPY --from=build-amd64 /go/src/github.com/ollama/ollama/dist/linux-amd64/lib/ /scratch/ +RUN cd /scratch/ollama/ && rm -rf rocblas libamd* libdrm* libroc* libhip* libhsa* # Runtime stages FROM --platform=linux/amd64 ubuntu:22.04 as runtime-amd64 +COPY --from=amd64-libs-without-rocm /scratch/ /lib/ RUN apt-get update && apt-get install -y ca-certificates -COPY --from=build-amd64 /go/src/github.com/ollama/ollama/dist/linux-amd64/ollama /bin/ollama +COPY --from=build-amd64 /go/src/github.com/ollama/ollama/dist/linux-amd64/bin/ /bin/ + FROM --platform=linux/arm64 ubuntu:22.04 as runtime-arm64 +COPY --from=build-arm64 /go/src/github.com/ollama/ollama/dist/linux-arm64/lib/ /lib/ RUN apt-get update && apt-get install -y ca-certificates -COPY --from=build-arm64 /go/src/github.com/ollama/ollama/dist/linux-arm64/ollama /bin/ollama +COPY --from=build-arm64 /go/src/github.com/ollama/ollama/dist/linux-arm64/bin/ /bin/ # Radeon images are much larger so we keep it distinct from the CPU/CUDA image FROM --platform=linux/amd64 rocm/dev-centos-7:${ROCM_VERSION}-complete as runtime-rocm RUN update-pciids -COPY --from=build-amd64 /go/src/github.com/ollama/ollama/dist/linux-amd64/ollama /bin/ollama +COPY --from=build-amd64 /go/src/github.com/ollama/ollama/dist/linux-amd64/bin/ /bin/ +RUN ln -s /opt/rocm/lib /lib/ollama EXPOSE 11434 ENV OLLAMA_HOST 0.0.0.0 diff --git a/app/ollama.iss b/app/ollama.iss index e9cf48ec6..bce0a337e 100644 --- a/app/ollama.iss +++ b/app/ollama.iss @@ -87,11 +87,11 @@ DialogFontSize=12 [Files] Source: ".\app.exe"; DestDir: "{app}"; DestName: "{#MyAppExeName}" ; Flags: ignoreversion 64bit -Source: "..\ollama.exe"; DestDir: "{app}"; Flags: ignoreversion 64bit -Source: "..\dist\windows-{#ARCH}\ollama_runners\*"; DestDir: "{app}\ollama_runners"; Flags: ignoreversion 64bit recursesubdirs +Source: "..\ollama.exe"; DestDir: "{app}\bin"; Flags: ignoreversion 64bit +Source: "..\dist\windows-{#ARCH}\lib\ollama\runners\*"; DestDir: "{app}\lib\ollama\runners"; Flags: ignoreversion 64bit recursesubdirs Source: "..\dist\ollama_welcome.ps1"; DestDir: "{app}"; Flags: ignoreversion Source: ".\assets\app.ico"; DestDir: "{app}"; Flags: ignoreversion -Source: "..\dist\windows-amd64\ollama_libs\*"; DestDir: "{app}\ollama_libs\"; Flags: ignoreversion recursesubdirs +Source: "..\dist\windows-amd64\lib\ollama\*"; DestDir: "{app}\lib\ollama\"; Flags: ignoreversion recursesubdirs [Icons] Name: "{group}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; IconFilename: "{app}\app.ico" @@ -99,7 +99,7 @@ Name: "{userstartup}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; IconFilen Name: "{userprograms}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; IconFilename: "{app}\app.ico" [Run] -Filename: "{cmd}"; Parameters: "/C set PATH={app};%PATH% & ""{app}\{#MyAppExeName}"""; Flags: postinstall nowait runhidden +Filename: "{cmd}"; Parameters: "/C set PATH={app}\bin;%PATH% & ""{app}\{#MyAppExeName}"""; Flags: postinstall nowait runhidden [UninstallRun] ; Filename: "{cmd}"; Parameters: "/C ""taskkill /im ''{#MyAppExeName}'' /f /t"; Flags: runhidden @@ -134,8 +134,8 @@ SetupAppRunningError=Another Ollama installer is running.%n%nPlease cancel or fi [Registry] Root: HKCU; Subkey: "Environment"; \ - ValueType: expandsz; ValueName: "Path"; ValueData: "{olddata};{app}"; \ - Check: NeedsAddPath('{app}') + ValueType: expandsz; ValueName: "Path"; ValueData: "{olddata};{app}\bin"; \ + Check: NeedsAddPath('{app}\bin') [Code] diff --git a/docs/linux.md b/docs/linux.md index ec7306560..3ed2bed07 100644 --- a/docs/linux.md +++ b/docs/linux.md @@ -20,13 +20,12 @@ GPU. ## Manual install -### Download the `ollama` binary +### Download the `ollama` tar file -Ollama is distributed as a self-contained binary. Download it to a directory in your PATH: +Ollama is distributed as a tar file including GPU library dependencies. ```bash -sudo curl -L https://ollama.com/download/ollama-linux-amd64 -o /usr/bin/ollama -sudo chmod +x /usr/bin/ollama +curl -fsSL https://ollama.com/download/ollama-linux-amd64.tgz | sudo tar -C /usr -zxf - ``` ### Adding Ollama as a startup service (recommended) @@ -96,8 +95,7 @@ curl -fsSL https://ollama.com/install.sh | sh Or by downloading the ollama binary: ```bash -sudo curl -L https://ollama.com/download/ollama-linux-amd64 -o /usr/bin/ollama -sudo chmod +x /usr/bin/ollama +curl -fsSL https://ollama.com/download/ollama-linux-amd64.tgz | sudo tar -C /usr -zxf - ``` ## Installing specific versions diff --git a/envconfig/config.go b/envconfig/config.go index 7f0976c07..7e45a4f51 100644 --- a/envconfig/config.go +++ b/envconfig/config.go @@ -174,7 +174,7 @@ func RunnersDir() (p string) { defer func() { if p == "" { - slog.Error("unable to locate llm runner directory. Set OLLAMA_RUNNERS_DIR to the location of 'ollama_runners'") + slog.Error("unable to locate llm runner directory. Set OLLAMA_RUNNERS_DIR to the location of 'ollama/runners'") } }() @@ -190,7 +190,7 @@ func RunnersDir() (p string) { } var paths []string - for _, root := range []string{filepath.Dir(exe), cwd} { + for _, root := range []string{filepath.Dir(exe), filepath.Join(filepath.Dir(exe), ".."), cwd} { paths = append(paths, root, filepath.Join(root, runtime.GOOS+"-"+runtime.GOARCH), @@ -200,7 +200,7 @@ func RunnersDir() (p string) { // Try a few variations to improve developer experience when building from source in the local tree for _, path := range paths { - candidate := filepath.Join(path, "ollama_runners") + candidate := filepath.Join(path, "lib", "ollama", "runners") if _, err := os.Stat(candidate); err == nil { p = candidate break diff --git a/gpu/amd_common.go b/gpu/amd_common.go index 05747208f..72d204f77 100644 --- a/gpu/amd_common.go +++ b/gpu/amd_common.go @@ -54,7 +54,7 @@ func commonAMDValidateLibDir() (string, error) { // Installer payload location if we're running the installed binary exe, err := os.Executable() if err == nil { - rocmTargetDir := filepath.Join(filepath.Dir(exe), "ollama_libs") + rocmTargetDir := filepath.Join(filepath.Dir(exe), "..", "lib", "ollama") if rocmLibUsable(rocmTargetDir) { slog.Debug("detected ROCM next to ollama executable " + rocmTargetDir) return rocmTargetDir, nil diff --git a/gpu/amd_windows.go b/gpu/amd_windows.go index 5d25a966f..a0ae7c960 100644 --- a/gpu/amd_windows.go +++ b/gpu/amd_windows.go @@ -153,7 +153,7 @@ func AMDValidateLibDir() (string, error) { // Installer payload (if we're running from some other location) localAppData := os.Getenv("LOCALAPPDATA") appDir := filepath.Join(localAppData, "Programs", "Ollama") - rocmTargetDir := filepath.Join(appDir, "ollama_libs") + rocmTargetDir := filepath.Join(appDir, "..", "lib", "ollama") if rocmLibUsable(rocmTargetDir) { slog.Debug("detected ollama installed ROCm at " + rocmTargetDir) return rocmTargetDir, nil diff --git a/gpu/gpu.go b/gpu/gpu.go index eb87807a7..391c98a86 100644 --- a/gpu/gpu.go +++ b/gpu/gpu.go @@ -653,8 +653,8 @@ func GetDepDir() string { slog.Warn("failed to lookup working directory", "error", err) } // Scan for any of our dependeices, and pick first match - for _, root := range []string{filepath.Dir(appExe), cwd} { - libDep := "ollama_libs" + for _, root := range []string{filepath.Dir(appExe), filepath.Join(filepath.Dir(appExe), ".."), cwd} { + libDep := filepath.Join("lib", "ollama") if _, err := os.Stat(filepath.Join(root, libDep)); err == nil { return filepath.Join(root, libDep) } diff --git a/llm/generate/gen_linux.sh b/llm/generate/gen_linux.sh index dc9dda5a3..aef03f9a5 100755 --- a/llm/generate/gen_linux.sh +++ b/llm/generate/gen_linux.sh @@ -189,7 +189,7 @@ if [ -z "${OLLAMA_SKIP_CUDA_GENERATE}" -a -d "${CUDA_LIB_DIR}" ]; then CMAKE_DEFS="${COMMON_CMAKE_DEFS} ${CMAKE_DEFS} ${ARM64_DEFS} ${CMAKE_CUDA_DEFS} -DGGML_STATIC=off" BUILD_DIR="../build/linux/${ARCH}/cuda${CUDA_VARIANT}" export LLAMA_SERVER_LDFLAGS="-L${CUDA_LIB_DIR} -lcudart -lcublas -lcublasLt -lcuda" - CUDA_DIST_DIR="${CUDA_DIST_DIR:-${DIST_BASE}/ollama_libs}" + CUDA_DIST_DIR="${CUDA_DIST_DIR:-${DIST_BASE}/lib/ollama}" build install echo "Installing CUDA dependencies in ${CUDA_DIST_DIR}" @@ -213,7 +213,7 @@ if [ -z "${OLLAMA_SKIP_ONEAPI_GENERATE}" -a -d "${ONEAPI_ROOT}" ]; then CC=icx CMAKE_DEFS="${COMMON_CMAKE_DEFS} ${CMAKE_DEFS} -DCMAKE_C_COMPILER=icx -DCMAKE_CXX_COMPILER=icpx -DGGML_SYCL=ON -DGGML_SYCL_F16=OFF" BUILD_DIR="../build/linux/${ARCH}/oneapi" - ONEAPI_DIST_DIR="${DIST_BASE}/ollama_libs" + ONEAPI_DIST_DIR="${DIST_BASE}/lib/ollama" export LLAMA_SERVER_LDFLAGS="-fsycl -lOpenCL -lmkl_core -lmkl_sycl_blas -lmkl_intel_ilp64 -lmkl_tbb_thread -ltbb" DEBUG_FLAGS="" # icx compiles with -O0 if we pass -g, so we must remove it build @@ -260,7 +260,7 @@ if [ -z "${OLLAMA_SKIP_ROCM_GENERATE}" -a -d "${ROCM_PATH}" ]; then echo "Building custom ROCM GPU" fi BUILD_DIR="../build/linux/${ARCH}/rocm${ROCM_VARIANT}" - ROCM_DIST_DIR="${DIST_BASE}/ollama_libs" + ROCM_DIST_DIR="${DIST_BASE}/lib/ollama" # TODO figure out how to disable runpath (rpath) # export CMAKE_HIP_FLAGS="-fno-rtlib-add-rpath" # doesn't work export LLAMA_SERVER_LDFLAGS="-L${ROCM_PATH}/lib -L/opt/amdgpu/lib/x86_64-linux-gnu/ -lhipblas -lrocblas -lamdhip64 -lrocsolver -lamd_comgr -lhsa-runtime64 -lrocsparse -ldrm -ldrm_amdgpu" diff --git a/llm/generate/gen_windows.ps1 b/llm/generate/gen_windows.ps1 index 42708d3e0..4d43c9e27 100644 --- a/llm/generate/gen_windows.ps1 +++ b/llm/generate/gen_windows.ps1 @@ -35,7 +35,7 @@ function init_vars { ) $script:commonCpuDefs = @("-DCMAKE_POSITION_INDEPENDENT_CODE=on") $script:ARCH = $Env:PROCESSOR_ARCHITECTURE.ToLower() - $script:DIST_BASE = "${script:SRC_DIR}\dist\windows-${script:ARCH}\ollama_runners" + $script:DIST_BASE = "${script:SRC_DIR}\dist\windows-${script:ARCH}\lib\ollama\runners" md "$script:DIST_BASE" -ea 0 > $null if ($env:CGO_CFLAGS -contains "-g") { $script:cmakeDefs += @("-DCMAKE_VERBOSE_MAKEFILE=on", "-DLLAMA_SERVER_VERBOSE=on", "-DCMAKE_BUILD_TYPE=RelWithDebInfo") @@ -286,11 +286,11 @@ function build_cuda() { sign install - md "${script:SRC_DIR}\dist\windows-${script:ARCH}\ollama_libs\" -ea 0 > $null - write-host "copying CUDA dependencies to ${script:SRC_DIR}\dist\windows-${script:ARCH}\ollama_libs\" - cp "${script:CUDA_LIB_DIR}\cudart64_*.dll" "${script:SRC_DIR}\dist\windows-${script:ARCH}\ollama_libs\" - cp "${script:CUDA_LIB_DIR}\cublas64_*.dll" "${script:SRC_DIR}\dist\windows-${script:ARCH}\ollama_libs\" - cp "${script:CUDA_LIB_DIR}\cublasLt64_*.dll" "${script:SRC_DIR}\dist\windows-${script:ARCH}\ollama_libs\" + md "${script:SRC_DIR}\dist\windows-${script:ARCH}\lib\ollama\" -ea 0 > $null + write-host "copying CUDA dependencies to ${script:SRC_DIR}\dist\windows-${script:ARCH}\lib\ollama\" + cp "${script:CUDA_LIB_DIR}\cudart64_*.dll" "${script:SRC_DIR}\dist\windows-${script:ARCH}\lib\ollama\" + cp "${script:CUDA_LIB_DIR}\cublas64_*.dll" "${script:SRC_DIR}\dist\windows-${script:ARCH}\lib\ollama\" + cp "${script:CUDA_LIB_DIR}\cublasLt64_*.dll" "${script:SRC_DIR}\dist\windows-${script:ARCH}\lib\ollama\" } else { write-host "Skipping CUDA generation step" } @@ -324,17 +324,17 @@ function build_oneapi() { sign install - md "${script:SRC_DIR}\dist\windows-${script:ARCH}\ollama_libs\" -ea 0 > $null - cp "${env:ONEAPI_ROOT}\compiler\latest\bin\libirngmd.dll" "${script:SRC_DIR}\dist\windows-${script:ARCH}\ollama_libs\" - cp "${env:ONEAPI_ROOT}\compiler\latest\bin\libmmd.dll" "${script:SRC_DIR}\dist\windows-${script:ARCH}\ollama_libs\" - cp "${env:ONEAPI_ROOT}\compiler\latest\bin\pi_level_zero.dll" "${script:SRC_DIR}\dist\windows-${script:ARCH}\ollama_libs\" - cp "${env:ONEAPI_ROOT}\compiler\latest\bin\pi_unified_runtime.dll" "${script:SRC_DIR}\dist\windows-${script:ARCH}\ollama_libs\" - cp "${env:ONEAPI_ROOT}\compiler\latest\bin\pi_win_proxy_loader.dll" "${script:SRC_DIR}\dist\windows-${script:ARCH}\ollama_libs\" - cp "${env:ONEAPI_ROOT}\compiler\latest\bin\svml_dispmd.dll" "${script:SRC_DIR}\dist\windows-${script:ARCH}\ollama_libs\" - cp "${env:ONEAPI_ROOT}\compiler\latest\bin\sycl7.dll" "${script:SRC_DIR}\dist\windows-${script:ARCH}\ollama_libs\" - cp "${env:ONEAPI_ROOT}\mkl\latest\bin\mkl_core.2.dll" "${script:SRC_DIR}\dist\windows-${script:ARCH}\ollama_libs\" - cp "${env:ONEAPI_ROOT}\mkl\latest\bin\mkl_sycl_blas.4.dll" "${script:SRC_DIR}\dist\windows-${script:ARCH}\ollama_libs\" - cp "${env:ONEAPI_ROOT}\mkl\latest\bin\mkl_tbb_thread.2.dll" "${script:SRC_DIR}\dist\windows-${script:ARCH}\ollama_libs\" + md "${script:SRC_DIR}\dist\windows-${script:ARCH}\lib\ollama\" -ea 0 > $null + cp "${env:ONEAPI_ROOT}\compiler\latest\bin\libirngmd.dll" "${script:SRC_DIR}\dist\windows-${script:ARCH}\lib\ollama\" + cp "${env:ONEAPI_ROOT}\compiler\latest\bin\libmmd.dll" "${script:SRC_DIR}\dist\windows-${script:ARCH}\lib\ollama\" + cp "${env:ONEAPI_ROOT}\compiler\latest\bin\pi_level_zero.dll" "${script:SRC_DIR}\dist\windows-${script:ARCH}\lib\ollama\" + cp "${env:ONEAPI_ROOT}\compiler\latest\bin\pi_unified_runtime.dll" "${script:SRC_DIR}\dist\windows-${script:ARCH}\lib\ollama\" + cp "${env:ONEAPI_ROOT}\compiler\latest\bin\pi_win_proxy_loader.dll" "${script:SRC_DIR}\dist\windows-${script:ARCH}\lib\ollama\" + cp "${env:ONEAPI_ROOT}\compiler\latest\bin\svml_dispmd.dll" "${script:SRC_DIR}\dist\windows-${script:ARCH}\lib\ollama\" + cp "${env:ONEAPI_ROOT}\compiler\latest\bin\sycl7.dll" "${script:SRC_DIR}\dist\windows-${script:ARCH}\lib\ollama\" + cp "${env:ONEAPI_ROOT}\mkl\latest\bin\mkl_core.2.dll" "${script:SRC_DIR}\dist\windows-${script:ARCH}\lib\ollama\" + cp "${env:ONEAPI_ROOT}\mkl\latest\bin\mkl_sycl_blas.4.dll" "${script:SRC_DIR}\dist\windows-${script:ARCH}\lib\ollama\" + cp "${env:ONEAPI_ROOT}\mkl\latest\bin\mkl_tbb_thread.2.dll" "${script:SRC_DIR}\dist\windows-${script:ARCH}\lib\ollama\" } else { Write-Host "Skipping oneAPI generation step" } @@ -384,11 +384,11 @@ function build_rocm() { sign install - md "${script:SRC_DIR}\dist\windows-${script:ARCH}\ollama_libs\rocblas\library\" -ea 0 > $null - cp "${env:HIP_PATH}\bin\hipblas.dll" "${script:SRC_DIR}\dist\windows-${script:ARCH}\ollama_libs\" - cp "${env:HIP_PATH}\bin\rocblas.dll" "${script:SRC_DIR}\dist\windows-${script:ARCH}\ollama_libs\" + md "${script:SRC_DIR}\dist\windows-${script:ARCH}\lib\ollama\rocblas\library\" -ea 0 > $null + cp "${env:HIP_PATH}\bin\hipblas.dll" "${script:SRC_DIR}\dist\windows-${script:ARCH}\lib\ollama\" + cp "${env:HIP_PATH}\bin\rocblas.dll" "${script:SRC_DIR}\dist\windows-${script:ARCH}\lib\ollama\" # amdhip64.dll dependency comes from the driver and must be installed on the host to use AMD GPUs - cp "${env:HIP_PATH}\bin\rocblas\library\*" "${script:SRC_DIR}\dist\windows-${script:ARCH}\ollama_libs\rocblas\library\" + cp "${env:HIP_PATH}\bin\rocblas\library\*" "${script:SRC_DIR}\dist\windows-${script:ARCH}\lib\ollama\rocblas\library\" } else { write-host "Skipping ROCm generation step" } diff --git a/scripts/build_windows.ps1 b/scripts/build_windows.ps1 index 50b602305..9cebf1f40 100644 --- a/scripts/build_windows.ps1 +++ b/scripts/build_windows.ps1 @@ -122,8 +122,8 @@ function buildOllama() { /csp "Google Cloud KMS Provider" /kc ${env:KEY_CONTAINER} ollama.exe if ($LASTEXITCODE -ne 0) { exit($LASTEXITCODE)} } - New-Item -ItemType Directory -Path .\dist\windows-${script:TARGET_ARCH}\ -Force - cp .\ollama.exe .\dist\windows-${script:TARGET_ARCH}\ + New-Item -ItemType Directory -Path .\dist\windows-${script:TARGET_ARCH}\bin\ -Force + cp .\ollama.exe .\dist\windows-${script:TARGET_ARCH}\bin\ } function buildApp() { @@ -142,22 +142,22 @@ function buildApp() { function gatherDependencies() { write-host "Gathering runtime dependencies" cd "${script:SRC_DIR}" - md "${script:DEPS_DIR}\ollama_libs" -ea 0 > $null + md "${script:DEPS_DIR}\lib\ollama" -ea 0 > $null # TODO - this varies based on host build system and MSVC version - drive from dumpbin output # currently works for Win11 + MSVC 2019 + Cuda V11 - cp "${env:VCToolsRedistDir}\x64\Microsoft.VC*.CRT\msvcp140*.dll" "${script:DEPS_DIR}\ollama_libs\" - cp "${env:VCToolsRedistDir}\x64\Microsoft.VC*.CRT\vcruntime140.dll" "${script:DEPS_DIR}\ollama_libs\" - cp "${env:VCToolsRedistDir}\x64\Microsoft.VC*.CRT\vcruntime140_1.dll" "${script:DEPS_DIR}\ollama_libs\" + cp "${env:VCToolsRedistDir}\x64\Microsoft.VC*.CRT\msvcp140*.dll" "${script:DEPS_DIR}\lib\ollama\" + cp "${env:VCToolsRedistDir}\x64\Microsoft.VC*.CRT\vcruntime140.dll" "${script:DEPS_DIR}\lib\ollama\" + cp "${env:VCToolsRedistDir}\x64\Microsoft.VC*.CRT\vcruntime140_1.dll" "${script:DEPS_DIR}\lib\ollama\" foreach ($part in $("runtime", "stdio", "filesystem", "math", "convert", "heap", "string", "time", "locale", "environment")) { - cp "$env:VCToolsRedistDir\..\..\..\Tools\Llvm\x64\bin\api-ms-win-crt-${part}*.dll" "${script:DEPS_DIR}\ollama_libs\" + cp "$env:VCToolsRedistDir\..\..\..\Tools\Llvm\x64\bin\api-ms-win-crt-${part}*.dll" "${script:DEPS_DIR}\lib\ollama\" } cp "${script:SRC_DIR}\app\ollama_welcome.ps1" "${script:SRC_DIR}\dist\" if ("${env:KEY_CONTAINER}") { write-host "about to sign" - foreach ($file in (get-childitem "${script:DEPS_DIR}\ollama_libs\cu*.dll") + @("${script:SRC_DIR}\dist\ollama_welcome.ps1")){ + foreach ($file in (get-childitem "${script:DEPS_DIR}\lib\ollama\cu*.dll") + @("${script:SRC_DIR}\dist\ollama_welcome.ps1")){ write-host "signing $file" & "${script:SignTool}" sign /v /fd sha256 /t http://timestamp.digicert.com /f "${script:OLLAMA_CERT}" ` /csp "Google Cloud KMS Provider" /kc ${env:KEY_CONTAINER} $file diff --git a/scripts/install.sh b/scripts/install.sh index f0439b003..a02a0675d 100644 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -66,7 +66,7 @@ fi for BINDIR in /usr/local/bin /usr/bin /bin; do echo $PATH | grep -q $BINDIR && break || continue done -OLLAMA_INSTALL_DIR=${OLLAMA_INSTALL_DIR:-${BINDIR}} +OLLAMA_INSTALL_DIR=$(dirname ${BINDIR}) status "Installing ollama to $OLLAMA_INSTALL_DIR" $SUDO install -o0 -g0 -m755 -d $BINDIR @@ -77,18 +77,22 @@ if curl -I --silent --fail --location "https://ollama.com/download/ollama-linux- "https://ollama.com/download/ollama-linux-${ARCH}.tgz${VER_PARAM}" | \ $SUDO tar -xzf - -C "$OLLAMA_INSTALL_DIR" BUNDLE=1 + if [ "$OLLAMA_INSTALL_DIR/bin/ollama" != "$BINDIR/ollama" ] ; then + status "Making ollama accessible in the PATH in $BINDIR" + $SUDO ln -sf "$OLLAMA_INSTALL_DIR/ollama" "$BINDIR/ollama" + fi else status "Downloading Linux ${ARCH} CLI" curl --fail --show-error --location --progress-bar -o "$TEMP_DIR/ollama"\ "https://ollama.com/download/ollama-linux-${ARCH}${VER_PARAM}" $SUDO install -o0 -g0 -m755 $TEMP_DIR/ollama $OLLAMA_INSTALL_DIR/ollama BUNDLE=0 + if [ "$OLLAMA_INSTALL_DIR/ollama" != "$BINDIR/ollama" ] ; then + status "Making ollama accessible in the PATH in $BINDIR" + $SUDO ln -sf "$OLLAMA_INSTALL_DIR/ollama" "$BINDIR/ollama" + fi fi -if [ "$OLLAMA_INSTALL_DIR/ollama" != "$BINDIR/ollama" ] ; then - status "Making ollama accessible in the PATH in $BINDIR" - $SUDO ln -sf "$OLLAMA_INSTALL_DIR/ollama" "$BINDIR/ollama" -fi install_success() { status 'The Ollama API is now available at 127.0.0.1:11434.' From f9e31da9463092d7b3661594788c259d6d55b3d9 Mon Sep 17 00:00:00 2001 From: Daniel Hiltgen Date: Thu, 15 Aug 2024 14:38:14 -0700 Subject: [PATCH 266/384] Review comments --- .github/workflows/release.yaml | 106 ++++++--------------------------- docs/linux.md | 8 +-- gpu/cuda_common.go | 2 +- gpu/gpu.go | 16 ++--- llm/generate/gen_windows.ps1 | 4 +- 5 files changed, 32 insertions(+), 104 deletions(-) diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 4bd684554..508fbb35e 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -183,10 +183,17 @@ jobs: name: windows-rocm-deps path: dist/deps/* - # CUDA v11 generation step - generate-windows-cuda-v11: + # CUDA generation step + generate-windows-cuda: environment: release runs-on: windows + strategy: + matrix: + cuda: + - version: "11" + url: 'https://developer.download.nvidia.com/compute/cuda/11.3.1/local_installers/cuda_11.3.1_465.89_win10.exe' + - version: "12" + url: 'https://developer.download.nvidia.com/compute/cuda/12.4.0/local_installers/cuda_12.4.0_551.61_windows.exe' env: KEY_CONTAINER: ${{ vars.KEY_CONTAINER }} steps: @@ -220,11 +227,11 @@ jobs: with: go-version-file: go.mod cache: true - - name: 'Install CUDA' + - name: 'Install CUDA ${{ matrix.cuda.version }}' run: | $ErrorActionPreference = "Stop" write-host "downloading CUDA Installer" - Invoke-WebRequest -Uri "https://developer.download.nvidia.com/compute/cuda/11.3.1/local_installers/cuda_11.3.1_465.89_win10.exe" -OutFile "${env:RUNNER_TEMP}\cuda-install.exe" + Invoke-WebRequest -Uri "${{ matrix.cuda.url }}" -OutFile "${env:RUNNER_TEMP}\cuda-install.exe" write-host "Installing CUDA" Start-Process "${env:RUNNER_TEMP}\cuda-install.exe" -ArgumentList '-s' -NoNewWindow -Wait write-host "Completed CUDA" @@ -256,7 +263,7 @@ jobs: cp "${NVIDIA_DIR}\cublasLt64_*.dll" "dist\deps\" - uses: actions/upload-artifact@v4 with: - name: generate-windows-cuda-v11 + name: generate-windows-cuda-${{ matrix.cuda.version }} path: | llm/build/**/bin/* dist/windows-amd64/** @@ -265,95 +272,13 @@ jobs: name: windows-cuda-deps path: dist/deps/* - # CUDA v12 generation step - generate-windows-cuda-v12: - environment: release - runs-on: windows - env: - KEY_CONTAINER: ${{ vars.KEY_CONTAINER }} - steps: - - uses: actions/checkout@v4 - - name: Set Version - shell: bash - run: echo "VERSION=${GITHUB_REF_NAME#v}" >> $GITHUB_ENV - - uses: 'google-github-actions/auth@v2' - with: - project_id: 'ollama' - credentials_json: '${{ secrets.GOOGLE_SIGNING_CREDENTIALS }}' - - run: echo "${{ vars.OLLAMA_CERT }}" > ollama_inc.crt - - name: install Windows SDK 8.1 to get signtool - run: | - $ErrorActionPreference = "Stop" - write-host "downloading SDK" - Invoke-WebRequest -Uri "https://go.microsoft.com/fwlink/p/?LinkId=323507" -OutFile "${env:RUNNER_TEMP}\sdksetup.exe" - Start-Process "${env:RUNNER_TEMP}\sdksetup.exe" -ArgumentList @("/q") -NoNewWindow -Wait - write-host "Win SDK 8.1 installed" - gci -path 'C:\Program Files (x86)\Windows Kits\' -r -fi 'signtool.exe' - - name: install signing plugin - run: | - $ErrorActionPreference = "Stop" - write-host "downloading plugin" - Invoke-WebRequest -Uri "https://github.com/GoogleCloudPlatform/kms-integrations/releases/download/cng-v1.0/kmscng-1.0-windows-amd64.zip" -OutFile "${env:RUNNER_TEMP}\plugin.zip" - Expand-Archive -Path "${env:RUNNER_TEMP}\plugin.zip" -DestinationPath ${env:RUNNER_TEMP}\plugin\ - write-host "Installing plugin" - & "${env:RUNNER_TEMP}\plugin\*\kmscng.msi" /quiet - write-host "plugin installed" - - uses: actions/setup-go@v5 - with: - go-version-file: go.mod - cache: true - - name: 'Install CUDA' - run: | - $ErrorActionPreference = "Stop" - write-host "downloading CUDA Installer" - Invoke-WebRequest -Uri "https://developer.download.nvidia.com/compute/cuda/12.4.0/local_installers/cuda_12.4.0_551.61_windows.exe" -OutFile "${env:RUNNER_TEMP}\cuda-install.exe" - write-host "Installing CUDA" - Start-Process "${env:RUNNER_TEMP}\cuda-install.exe" -ArgumentList '-s' -NoNewWindow -Wait - write-host "Completed CUDA" - $cudaPath=((resolve-path "c:\Program Files\NVIDIA*\CUDA\v*\bin\nvcc.exe")[0].path | split-path | split-path) - $cudaVer=($cudaPath | split-path -leaf ) -replace 'v(\d+).(\d+)', '$1_$2' - echo "$cudaPath\bin" >> $env:GITHUB_PATH - echo "CUDA_PATH=$cudaPath" >> $env:GITHUB_ENV - echo "CUDA_PATH_V${cudaVer}=$cudaPath" >> $env:GITHUB_ENV - echo "CUDA_PATH_VX_Y=CUDA_PATH_V${cudaVer}" >> $env:GITHUB_ENV - - name: 'Verify CUDA' - run: nvcc -V - - run: go get ./... - - name: go generate - run: | - $gopath=(get-command go).source | split-path -parent - $cudabin=(get-command nvcc).source | split-path - & "C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\Tools\Launch-VsDevShell.ps1" - cd $env:GITHUB_WORKSPACE - $env:CMAKE_SYSTEM_VERSION="10.0.22621.0" - $env:PATH="$gopath;$cudabin;$env:PATH" - $env:OLLAMA_SKIP_CPU_GENERATE="1" - go generate -x ./... - - name: 'gather cuda dependencies' - run: | - $NVIDIA_DIR=(resolve-path 'C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\*\bin\')[0] - md "dist\deps" - cp "${NVIDIA_DIR}\cudart64_*.dll" "dist\deps\" - cp "${NVIDIA_DIR}\cublas64_*.dll" "dist\deps\" - cp "${NVIDIA_DIR}\cublasLt64_*.dll" "dist\deps\" - - uses: actions/upload-artifact@v4 - with: - name: generate-windows-cuda-v12 - path: | - llm/build/**/bin/* - dist/windows-amd64/** - - uses: actions/upload-artifact@v4 - with: - name: windows-cuda-deps - path: dist/deps/* # Import the prior generation steps and build the final windows assets build-windows: environment: release runs-on: windows needs: - - generate-windows-cuda-v11 - - generate-windows-cuda-v12 + - generate-windows-cuda - generate-windows-rocm - generate-windows-cpu env: @@ -397,7 +322,10 @@ jobs: name: generate-windows-cpu - uses: actions/download-artifact@v4 with: - name: generate-windows-cuda-v11 + name: generate-windows-cuda-11 + - uses: actions/download-artifact@v4 + with: + name: generate-windows-cuda-12 - uses: actions/download-artifact@v4 with: name: windows-cuda-deps diff --git a/docs/linux.md b/docs/linux.md index 3ed2bed07..d1d5892c4 100644 --- a/docs/linux.md +++ b/docs/linux.md @@ -20,12 +20,12 @@ GPU. ## Manual install -### Download the `ollama` tar file +### Download `ollama` -Ollama is distributed as a tar file including GPU library dependencies. +Download and extract the Linux package: ```bash -curl -fsSL https://ollama.com/download/ollama-linux-amd64.tgz | sudo tar -C /usr -zxf - +curl -fsSL https://ollama.com/download/ollama-linux-amd64.tgz | sudo tar zx -C /usr ``` ### Adding Ollama as a startup service (recommended) @@ -95,7 +95,7 @@ curl -fsSL https://ollama.com/install.sh | sh Or by downloading the ollama binary: ```bash -curl -fsSL https://ollama.com/download/ollama-linux-amd64.tgz | sudo tar -C /usr -zxf - +curl -fsSL https://ollama.com/download/ollama-linux-amd64.tgz | sudo tar zx -C /usr ``` ## Installing specific versions diff --git a/gpu/cuda_common.go b/gpu/cuda_common.go index defaa60a3..827cc9b48 100644 --- a/gpu/cuda_common.go +++ b/gpu/cuda_common.go @@ -28,7 +28,7 @@ func cudaGetVisibleDevicesEnv(gpuInfo []GpuInfo) (string, string) { return "CUDA_VISIBLE_DEVICES", strings.Join(ids, ",") } -func cudaGetVariant(gpuInfo CudaGPUInfo) string { +func cudaVariant(gpuInfo CudaGPUInfo) string { if runtime.GOARCH == "arm64" && runtime.GOOS == "linux" { if CudaTegra != "" { ver := strings.Split(CudaTegra, ".") diff --git a/gpu/gpu.go b/gpu/gpu.go index 391c98a86..72d237a6c 100644 --- a/gpu/gpu.go +++ b/gpu/gpu.go @@ -225,7 +225,7 @@ func GetGPUInfo() GpuInfoList { return GpuInfoList{cpus[0].GpuInfo} } - depPath := GetDepDir() + depPath := LibraryDir() // Load ALL libraries cHandles = initCudaHandles() @@ -264,20 +264,20 @@ func GetGPUInfo() GpuInfoList { gpuInfo.computeMajor = int(memInfo.major) gpuInfo.computeMinor = int(memInfo.minor) gpuInfo.MinimumMemory = cudaMinimumMemory - cudaVariant := cudaGetVariant(gpuInfo) + variant := cudaVariant(gpuInfo) if depPath != "" { gpuInfo.DependencyPath = depPath // Check for variant specific directory - if cudaVariant != "" { - if _, err := os.Stat(filepath.Join(depPath, "cuda_"+cudaVariant)); err == nil { - gpuInfo.DependencyPath = filepath.Join(depPath, "cuda_"+cudaVariant) + if variant != "" { + if _, err := os.Stat(filepath.Join(depPath, "cuda_"+variant)); err == nil { + gpuInfo.DependencyPath = filepath.Join(depPath, "cuda_"+variant) } } } gpuInfo.Name = C.GoString(&memInfo.gpu_name[0]) gpuInfo.DriverMajor = driverMajor gpuInfo.DriverMinor = driverMinor - gpuInfo.Variant = cudaGetVariant(gpuInfo) + gpuInfo.Variant = variant // query the management library as well so we can record any skew between the two // which represents overhead on the GPU we must set aside on subsequent updates @@ -468,7 +468,7 @@ func FindGPULibs(baseLibName string, defaultPatterns []string) []string { slog.Debug("Searching for GPU library", "name", baseLibName) // Start with our bundled libraries - patterns := []string{filepath.Join(GetDepDir(), baseLibName)} + patterns := []string{filepath.Join(LibraryDir(), baseLibName)} switch runtime.GOOS { case "windows": @@ -642,7 +642,7 @@ func (l GpuInfoList) GetVisibleDevicesEnv() (string, string) { } } -func GetDepDir() string { +func LibraryDir() string { // On Windows/linux we bundle the dependencies at the same level as the executable appExe, err := os.Executable() if err != nil { diff --git a/llm/generate/gen_windows.ps1 b/llm/generate/gen_windows.ps1 index 4d43c9e27..cbdfd09f1 100644 --- a/llm/generate/gen_windows.ps1 +++ b/llm/generate/gen_windows.ps1 @@ -117,7 +117,7 @@ function build { if ($cmakeDefs -contains "-G") { $extra=@("-j8") } else { - $extra= @("--", "/p:CL_MPcount=8") + $extra= @("--", "/maxCpuCount:8") } write-host "building with: cmake --build $script:buildDir --config $script:config $($script:cmakeTargets | ForEach-Object { `"--target`", $_ }) $extra" & cmake --build $script:buildDir --config $script:config ($script:cmakeTargets | ForEach-Object { "--target", $_ }) $extra @@ -273,7 +273,7 @@ function build_cuda() { "-DGGML_CUDA=ON", "-DGGML_AVX=on", "-DGGML_AVX2=off", - "-DCMAKE_CUDA_FLAGS=-t8", + "-DCMAKE_CUDA_FLAGS=-t6", "-DCMAKE_CUDA_ARCHITECTURES=${script:CMAKE_CUDA_ARCHITECTURES}", "-DCMAKE_CUDA_COMPILER_TOOLKIT_ROOT=$env:CUDA_PATH" ) From d8be22e47d460d1483846e2effb9b67fbfce1c0b Mon Sep 17 00:00:00 2001 From: Daniel Hiltgen Date: Mon, 19 Aug 2024 12:07:18 -0700 Subject: [PATCH 267/384] Fix overlapping artifact name on CI --- .github/workflows/release.yaml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 508fbb35e..f6489dacb 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -269,7 +269,7 @@ jobs: dist/windows-amd64/** - uses: actions/upload-artifact@v4 with: - name: windows-cuda-deps + name: windows-cuda-deps-${{ matrix.cuda.version }} path: dist/deps/* @@ -328,7 +328,10 @@ jobs: name: generate-windows-cuda-12 - uses: actions/download-artifact@v4 with: - name: windows-cuda-deps + name: windows-cuda-deps-11 + - uses: actions/download-artifact@v4 + with: + name: windows-cuda-deps-12 - uses: actions/download-artifact@v4 with: name: windows-rocm-deps From f91c9e370923d3b10a88732ab577e2728022152d Mon Sep 17 00:00:00 2001 From: Daniel Hiltgen Date: Mon, 19 Aug 2024 13:48:45 -0700 Subject: [PATCH 268/384] CI: handle directories during checksum (#6427) --- .github/workflows/release.yaml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index f6489dacb..aad49d988 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -472,7 +472,8 @@ jobs: merge-multiple: true - run: | ls -lh dist/ - (cd dist; sha256sum * > sha256sum.txt) + (cd dist; find . -type f | xargs sha256sum > ../sha256sum.txt) + mv sha256sum.txt dist/ cat dist/sha256sum.txt - name: Create or update Release run: | From 19e5a890f70b95a55c9de6a55357d78fc0a4ff81 Mon Sep 17 00:00:00 2001 From: Daniel Hiltgen Date: Mon, 19 Aug 2024 15:19:21 -0700 Subject: [PATCH 269/384] CI: remove directories from dist dir before upload step (#6429) --- .github/workflows/release.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index aad49d988..2cf4d2c2a 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -474,6 +474,7 @@ jobs: ls -lh dist/ (cd dist; find . -type f | xargs sha256sum > ../sha256sum.txt) mv sha256sum.txt dist/ + mv dist/linux-???64 . cat dist/sha256sum.txt - name: Create or update Release run: | From a017cf2fea4aaa376087520382058c42cffce097 Mon Sep 17 00:00:00 2001 From: Daniel Hiltgen Date: Tue, 20 Aug 2024 07:26:38 -0700 Subject: [PATCH 270/384] Split rocm back out of bundle (#6432) We're over budget for github's maximum release artifact size with rocm + 2 cuda versions. This splits rocm back out as a discrete artifact, but keeps the layout so it can be extracted into the same location as the main bundle. --- .github/workflows/release.yaml | 1 + Dockerfile | 4 ++-- llm/generate/gen_linux.sh | 3 ++- scripts/build_linux.sh | 6 ++++++ scripts/install.sh | 5 +++++ 5 files changed, 16 insertions(+), 3 deletions(-) diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 2cf4d2c2a..9c1e3e138 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -475,6 +475,7 @@ jobs: (cd dist; find . -type f | xargs sha256sum > ../sha256sum.txt) mv sha256sum.txt dist/ mv dist/linux-???64 . + mv dist/linux-amd64-rocm . cat dist/sha256sum.txt - name: Create or update Release run: | diff --git a/Dockerfile b/Dockerfile index d4b869180..c46477b49 100644 --- a/Dockerfile +++ b/Dockerfile @@ -95,8 +95,8 @@ ARG AMDGPU_TARGETS ENV GOARCH amd64 RUN --mount=type=cache,target=/root/.ccache \ OLLAMA_SKIP_STATIC_GENERATE=1 OLLAMA_SKIP_CPU_GENERATE=1 bash gen_linux.sh -RUN mkdir -p ../../dist/linux-amd64/lib/ollama && \ - (cd /opt/rocm/lib && tar cf - rocblas/library) | (cd ../../dist/linux-amd64/lib/ollama && tar xf - ) +RUN mkdir -p ../../dist/linux-amd64-rocm/lib/ollama && \ + (cd /opt/rocm/lib && tar cf - rocblas/library) | (cd ../../dist/linux-amd64-rocm/lib/ollama && tar xf - ) FROM --platform=linux/amd64 centos:7 AS cpu-builder-amd64 ARG CMAKE_VERSION diff --git a/llm/generate/gen_linux.sh b/llm/generate/gen_linux.sh index aef03f9a5..6927dda8b 100755 --- a/llm/generate/gen_linux.sh +++ b/llm/generate/gen_linux.sh @@ -260,7 +260,8 @@ if [ -z "${OLLAMA_SKIP_ROCM_GENERATE}" -a -d "${ROCM_PATH}" ]; then echo "Building custom ROCM GPU" fi BUILD_DIR="../build/linux/${ARCH}/rocm${ROCM_VARIANT}" - ROCM_DIST_DIR="${DIST_BASE}/lib/ollama" + # ROCm dependencies are too large to fit into a unified bundle + ROCM_DIST_DIR="${DIST_BASE}/../linux-${GOARCH}-rocm/lib/ollama" # TODO figure out how to disable runpath (rpath) # export CMAKE_HIP_FLAGS="-fno-rtlib-add-rpath" # doesn't work export LLAMA_SERVER_LDFLAGS="-L${ROCM_PATH}/lib -L/opt/amdgpu/lib/x86_64-linux-gnu/ -lhipblas -lrocblas -lamdhip64 -lrocsolver -lamd_comgr -lhsa-runtime64 -lrocsparse -ldrm -ldrm_amdgpu" diff --git a/scripts/build_linux.sh b/scripts/build_linux.sh index adda2ad75..6cb0d0cd0 100755 --- a/scripts/build_linux.sh +++ b/scripts/build_linux.sh @@ -24,8 +24,14 @@ for TARGETARCH in ${BUILD_ARCH}; do docker create --platform linux/$TARGETARCH --name builder-$TARGETARCH builder:$TARGETARCH rm -rf ./dist/linux-$TARGETARCH docker cp builder-$TARGETARCH:/go/src/github.com/ollama/ollama/dist/linux-$TARGETARCH ./dist + if echo ${TARGETARCH} | grep "amd64" > /dev/null; then + docker cp builder-$TARGETARCH:/go/src/github.com/ollama/ollama/dist/linux-$TARGETARCH-rocm ./dist + fi docker rm builder-$TARGETARCH echo "Compressing final linux bundle..." rm -f ./dist/ollama-linux-$TARGETARCH.tgz (cd dist/linux-$TARGETARCH && tar cf - . | ${GZIP} --best > ../ollama-linux-$TARGETARCH.tgz ) + if [ -d dist/linux-$TARGETARCH-rocm ]; then + (cd dist/linux-$TARGETARCH-rocm && tar cf - . | ${GZIP} --best > ../ollama-linux-$TARGETARCH-rocm.tgz ) + fi done diff --git a/scripts/install.sh b/scripts/install.sh index a02a0675d..25f57565a 100644 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -199,6 +199,11 @@ fi if check_gpu lspci amdgpu || check_gpu lshw amdgpu; then if [ $BUNDLE -ne 0 ]; then + status "Downloading Linux ROCm ${ARCH} bundle" + curl --fail --show-error --location --progress-bar \ + "https://ollama.com/download/ollama-linux-${ARCH}-rocm.tgz${VER_PARAM}" | \ + $SUDO tar -xzf - -C "$OLLAMA_INSTALL_DIR" + install_success status "AMD GPU ready." exit 0 From 5a28b9cf5fcb3994aa1a143118c73c7d1fbf3bf9 Mon Sep 17 00:00:00 2001 From: Michael Yang Date: Thu, 6 Jun 2024 08:59:04 -0700 Subject: [PATCH 271/384] bert --- convert/convert.go | 12 ++ convert/convert_bert.go | 176 +++++++++++++++++++++++++ convert/convert_test.go | 1 + convert/reader.go | 2 + convert/testdata/all-MiniLM-L6-v2.json | 124 +++++++++++++++++ convert/tokenizer.go | 31 ++--- 6 files changed, 331 insertions(+), 15 deletions(-) create mode 100644 convert/convert_bert.go create mode 100644 convert/testdata/all-MiniLM-L6-v2.json diff --git a/convert/convert.go b/convert/convert.go index 24c19aa4d..f51e96658 100644 --- a/convert/convert.go +++ b/convert/convert.go @@ -66,6 +66,10 @@ type Converter interface { writeFile(io.WriteSeeker, llm.KV, []llm.Tensor) error } +type moreParser interface { + parseMore(fs.FS) error +} + // Convert writes an Ollama compatible model to the provided io.WriteSeeker based on configurations // and files it finds in the input path. // Supported input model formats include safetensors. @@ -95,6 +99,8 @@ func Convert(fsys fs.FS, ws io.WriteSeeker) error { conv = &gemma{} case "Phi3ForCausalLM": conv = &phi3{} + case "BertModel": + conv = &bert{} default: return errors.New("unsupported architecture") } @@ -103,6 +109,12 @@ func Convert(fsys fs.FS, ws io.WriteSeeker) error { return err } + if t, ok := conv.(moreParser); ok { + if err := t.parseMore(fsys); err != nil { + return err + } + } + t, err := parseTokenizer(fsys, conv.specialTokenTypes()) if err != nil { return err diff --git a/convert/convert_bert.go b/convert/convert_bert.go new file mode 100644 index 000000000..62fad1472 --- /dev/null +++ b/convert/convert_bert.go @@ -0,0 +1,176 @@ +package convert + +import ( + "cmp" + "encoding/json" + "io/fs" + "path/filepath" + "slices" + "strings" + + "github.com/ollama/ollama/llm" +) + +type bert struct { + Parameters + NLayers uint32 `json:"n_layers"` + NumHiddenLayers uint32 `json:"num_hidden_layers"` + NLayer uint32 `json:"n_layer"` + MaxPositionEmbeddings uint32 `json:"max_position_embeddings"` + NCtx uint32 `json:"n_ctx"` + HiddenSize uint32 `json:"hidden_size"` + NEmbd uint32 `json:"n_embd"` + IntermediateSize uint32 `json:"intermediate_size"` + NInner uint32 `json:"n_inner"` + NumAttentionHeads uint32 `json:"num_attention_heads"` + NHead uint32 `json:"n_head"` + NumKeyValueHeads uint32 `json:"num_key_value_heads"` + LayerNormEPS float32 `json:"layer_norm_eps"` + LayerNormEpsilon float32 `json:"layer_norm_epsilon"` + NormEpsilon float32 `json:"norm_epsilon"` + + PoolingType uint32 +} + +var ( + _ Converter = (*bert)(nil) + _ moreParser = (*bert)(nil) +) + +func (p *bert) parseMore(fsys fs.FS) error { + bts, err := fs.ReadFile(fsys, "modules.json") + if err != nil { + return err + } + + var modules []struct { + Type string `json:"type"` + Path string `json:"path"` + } + + if err := json.Unmarshal(bts, &modules); err != nil { + return err + } + + var pooling string + for _, m := range modules { + if m.Type == "sentence_transformers.models.Pooling" { + pooling = m.Path + break + } + } + + if pooling != "" { + bts, err := fs.ReadFile(fsys, filepath.Join(pooling, "config.json")) + if err != nil { + return err + } + + var pc struct { + PoolingModeCLSToken bool `json:"pooling_mode_cls_token"` + PoolingModeMeanTokens bool `json:"pooling_mode_mean_tokens"` + } + + if err := json.Unmarshal(bts, &pc); err != nil { + return err + } + + if pc.PoolingModeMeanTokens { + p.PoolingType = 1 + } else if pc.PoolingModeCLSToken { + p.PoolingType = 2 + } + } + + return nil +} + +func (p *bert) KV(t *Tokenizer) llm.KV { + kv := p.Parameters.KV(t) + kv["general.architecture"] = "bert" + kv["general.name"] = "bert" + kv["bert.attention.causal"] = false + kv["bert.pooling_type"] = p.PoolingType + + kv["bert.block_count"] = cmp.Or(p.NLayers, p.NumHiddenLayers, p.NLayer) + + if contextLength := cmp.Or(p.MaxPositionEmbeddings, p.NCtx); contextLength > 0 { + kv["bert.context_length"] = contextLength + } + + if embeddingLength := cmp.Or(p.HiddenSize, p.NEmbd); embeddingLength > 0 { + kv["bert.embedding_length"] = cmp.Or(p.HiddenSize, p.NEmbd) + } + + if feedForwardLength := cmp.Or(p.IntermediateSize, p.NInner); feedForwardLength > 0 { + kv["bert.feed_forward_length"] = cmp.Or(p.IntermediateSize, p.NInner) + } + + if headCount := cmp.Or(p.NumAttentionHeads, p.NHead); headCount > 0 { + kv["bert.attention.head_count"] = cmp.Or(p.NumAttentionHeads, p.NHead) + } + + if layerNormEpsilon := cmp.Or(p.LayerNormEPS, p.LayerNormEpsilon, p.NormEpsilon); layerNormEpsilon > 0 { + kv["bert.attention.layer_norm_epsilon"] = layerNormEpsilon + } + + kv["tokenizer.ggml.model"] = "bert" + kv["tokenizer.ggml.token_type_count"] = uint32(2) + + // convert to phantom space tokens + for i, e := range t.Tokens { + if strings.HasPrefix(e, "[") && strings.HasSuffix(e, "]") { + // noop + } else if strings.HasPrefix(e, "##") { + t.Tokens[i] = e[2:] + } else { + t.Tokens[i] = "\u2581" + e + } + } + + kv["tokenizer.ggml.tokens"] = t.Tokens + + return kv +} + +func (p *bert) Tensors(ts []Tensor) []llm.Tensor { + var out []llm.Tensor + for _, t := range ts { + if slices.Contains([]string{ + "embeddings.position_ids", + "pooler.dense.weight", + "pooler.dense.bias", + }, t.Name()) { + continue + } + + name := p.tensorName(t.Name()) + out = append(out, llm.Tensor{ + Name: name, + Kind: t.Kind(), + Shape: t.Shape(), + WriterTo: t, + }) + } + + return out +} + +func (bert) tensorName(n string) string { + return strings.NewReplacer( + "encoder.layer", "blk", + "encoder.layers", "blk", + "embeddings.word_embeddings", "token_embd", + "embeddings.token_type_embeddings", "token_types", + "embeddings.LayerNorm", "token_embd_norm", + "embeddings.position_embeddings", "position_embd", + "attention.self.query", "attn_q", + "attention.self.key", "attn_k", + "attention.self.value", "attn_v", + "attention.output.dense", "attn_output", + "attention.output.LayerNorm", "attn_output_norm", + "intermediate.dense", "ffn_up", + "output.dense", "ffn_down", + "output.LayerNorm", "layer_output_norm", + ).Replace(n) +} diff --git a/convert/convert_test.go b/convert/convert_test.go index cb2c585ef..e3ab00982 100644 --- a/convert/convert_test.go +++ b/convert/convert_test.go @@ -67,6 +67,7 @@ func TestConvertFull(t *testing.T) { "gemma-2b-it", // microsoft/Phi-3-mini-128-instruct@d548c233192db00165d842bf8edff054bb3212f8 "Phi-3-mini-128k-instruct", + "all-MiniLM-L6-v2", } for i := range cases { diff --git a/convert/reader.go b/convert/reader.go index ce95208e1..294a7c40b 100644 --- a/convert/reader.go +++ b/convert/reader.go @@ -37,6 +37,8 @@ const ( func (t tensorBase) Kind() uint32 { if strings.HasSuffix(t.name, ".block_sparse_moe.gate.weight") { return 0 + } else if t.name == "embeddings.token_type_embeddings.weight" { + return 0 } switch len(t.shape) { diff --git a/convert/testdata/all-MiniLM-L6-v2.json b/convert/testdata/all-MiniLM-L6-v2.json new file mode 100644 index 000000000..15c8f039c --- /dev/null +++ b/convert/testdata/all-MiniLM-L6-v2.json @@ -0,0 +1,124 @@ +{ + "general.architecture": "bert", + "general.file_type": "1", + "general.quantization_version": "2", + "bert.attention.causal": "false", + "bert.attention.head_count": "12", + "bert.attention.layer_norm_epsilon": "1e-12", + "bert.block_count": "6", + "bert.context_length": "512", + "bert.embedding_length": "384", + "bert.feed_forward_length": "1536", + "bert.pooling_type": "1", + "tokenizer.ggml.model": "bert", + "tokenizer.ggml.padding_token_id": "0", + "tokenizer.ggml.unknown_token_id": "100", + "tokenizer.ggml.cls_token_id": "101", + "tokenizer.ggml.seperator_token_id": "102", + "tokenizer.ggml.mask_token_id": "103", + "tokenizer.ggml.token_type_count": "2", + "tokenizer.ggml.scores": "6db964fe67338aca57790481a390121ff3dd643eebe49f7dd308029ad99abb6f", + "tokenizer.ggml.token_type": "98d247c5404b6b18f05f133b92dd56edf6efefefac326794b00d7b351f6c5aa1", + "tokenizer.ggml.tokens": "9efe405e229a45ff9916f54c475d151d2200cd2ab0006f347abfb069cf096c86", + "token_embd.weight": "8c1ee80a9ea4f65aa385ba30112010068af3d209bebc6e149d3d4589c2cd0a5a", + "position_embd.weight": "6c516f0b1c4e2388ab90394dd80ad69e4e4509b890982fc3408108ae66210eb6", + "token_types.weight": "f879f8e422ed211948f28b560d3c5e17aae7993f063b51196a28cf5c0fb3da21", + "token_embd_norm.weight": "75076e095d717aab96f8b6beeee503c27940d9a76f2b891a0e3de72f8a6043e4", + "token_embd_norm.bias": "298735285ffe944e1bf03e5d35c7280326b85cf121bde9874f1af5dc51ab939d", + "blk.0.attn_q.weight": "ab0923ce4c1549175112dcdfcc860fe30137f991e03ea6857fb5993670adaf6c", + "blk.0.attn_q.bias": "a3ec29551dabf976e1d34256b8ab5ab7b758f3ed9742c3cafdbd984d5441df62", + "blk.0.attn_k.weight": "4c1038a6d035c3e9ffed7fa672b614627814752503755fbad0cfb76a41ad71ba", + "blk.0.attn_k.bias": "e0363930eb588d91816aa3d230bb03b6e2551c165117b80b8d60397413819ef9", + "blk.0.attn_v.weight": "425e2e53e3f00ce98d29c3e6a161eb55d3e6ae0d96fdb9f6242d1c4fd6eef4b3", + "blk.0.attn_v.bias": "6579173a1e65ee124fbd0bd53cbdca4225515b4f2c5f18fb1bfd000f5978f9bb", + "blk.0.attn_output.weight": "a6d70a08cd7164de5d12af65d86d657c3db35aaecde778b2b3fda9193c4c9802", + "blk.0.attn_output.bias": "2b8d12c4f9a9c5bfaa29c597839568f6e0525cb41eeaf64ddeb6bd84dfeb9701", + "blk.0.attn_output_norm.weight": "bbe6e502a473228b525aeed26cc31b7db123ad63bdc5a6eebac6ea70b8b51d62", + "blk.0.attn_output_norm.bias": "36eaacaf0007c5c62daea97aab0115390c0682914f78482e37eb76885f4b7a50", + "blk.0.ffn_up.weight": "24654561c76ce387d125759ba843f06b904ef721fcceaeff6ccc62180a48e874", + "blk.0.ffn_up.bias": "fd3f0126aa1d95768fa60eb6f4ab8a2763cfcb7e5405f35b92353031d86f4d34", + "blk.0.ffn_down.weight": "97a829763a6a5bf3329ceb4d39c424ba4787d61653a5b0bbd1f84782e4d4e0ca", + "blk.0.ffn_down.bias": "7aa980c30ae8b4ee7f69df28808dbf5c431f56ccc4a80340f644a0419f16c054", + "blk.0.layer_output_norm.weight": "ef30dad4c2a083ae1ff5039a2a6cda60ecc89bf1e486a6f8c0d15f50589603f8", + "blk.0.layer_output_norm.bias": "8b1b77e67568b1bce43fc476de1b177c53ff688d66beb66995e8eb3dc290da8a", + "blk.1.attn_q.weight": "284331622a1f6f9b87ccee4f652bd66a394ca493c4d93be4d1844e4f6159ad10", + "blk.1.attn_q.bias": "e24ebd4860330e08f6bfdd077a82db0bee33f4c8846cf1db26327a34754c7069", + "blk.1.attn_k.weight": "729dd0d555544b5bd0f7580b3c8b384256b974605f0e7487b95f295aa032997d", + "blk.1.attn_k.bias": "2aa51a828a858f35473f54477583fea54ce2ccc34ea60fbd1d228fbe9bca827f", + "blk.1.attn_v.weight": "6be304671cc311d5ca5c103f2b51467ee800c589bc5b8101e09ff5aed1f68c21", + "blk.1.attn_v.bias": "43bcbab78a8819e07f723bc9e5b737b71e87a7594f15234e882b63e327a64199", + "blk.1.attn_output.weight": "15ec8a1a12b26c9976445308a09f748ab0e4bef0f583d13ab08c3129f8738d73", + "blk.1.attn_output.bias": "dac2146f4baa6ed16f6c0dc7443831fb7ec79bedcceafd80d1a4b628a1bb072d", + "blk.1.attn_output_norm.weight": "d2151eb33bffac536787a4c9a5d2b31c7a80b17c4611877842a3cce2cd6e98d8", + "blk.1.attn_output_norm.bias": "31e1b779716dafb855d2cf5631ee168a0ccf372eb9c6ea6091f66fa97a9b9d2d", + "blk.1.ffn_up.weight": "a57547fc3fc3b77406f5cdcb0c87af9bc184701f175c39c1f35297826fce3cc7", + "blk.1.ffn_up.bias": "123be6d541d086202913c75d878c54d59a749f3af7b58f7ef9eb9e7c62a24c9a", + "blk.1.ffn_down.weight": "cfdb79788377e5cbded8790cd41b9e66c397ecab75474071fcd7cf32d30f9613", + "blk.1.ffn_down.bias": "bcb58315519a573097960891c9ae41cf4c685ab78c3e0e77471471758a7eae88", + "blk.1.layer_output_norm.weight": "819b554271452bfb1d84c2603b90377b2e41a0ac1e3aa8b417ccf9dce63375bd", + "blk.1.layer_output_norm.bias": "47a3433ac27f5ce8947fb38dd491f3706df4ef6adb0ddf74612bf0f54b19e164", + "blk.2.attn_q.weight": "1557a9ea852b1880551f7290e00aded4f35e6c4180fdcbed1b0039bf805f639e", + "blk.2.attn_q.bias": "c3bfe5f3066f655fd36b055530997b59ff33ef013563aaeb3cb8ff07dabd59a9", + "blk.2.attn_k.weight": "cfd08eb69c61ae2f9f14f9b7ff5c5394ca264b1a9f3d48156677f90dd1766289", + "blk.2.attn_k.bias": "9b839bc0e79974a0b3f5d1895972bc6f5c9a1bc16052e1af786e6a530758152d", + "blk.2.attn_v.weight": "02b26b1208480eaeeb00e7b4cf8b690006ca14759357fc44ed4a2a8924ead993", + "blk.2.attn_v.bias": "e7e6f0089fded1659a867ab736c220d9653ea7da6b1b94baf5c8d30a748b63ab", + "blk.2.attn_output.weight": "a1db121c7d33806b349cadd050300a57db49fdc91224fd07c9ac43bf4299dc79", + "blk.2.attn_output.bias": "7675128b6a92555cd955c820311e91e9417d31f48848f45d047b4100c62148b3", + "blk.2.attn_output_norm.weight": "5b4595e0fbcba67a700c4331adf746d2fba3546364a4db5607ae241947bb1a21", + "blk.2.attn_output_norm.bias": "7b8e16826ea30e5a2ba0b02e0095a901775981a296e98819625320e983060d08", + "blk.2.ffn_up.weight": "a0d815d946ac07a65095c4ae4df77b818845e6d97795c7d82f55e689d944db59", + "blk.2.ffn_up.bias": "ce37c0a4174d6bf773ded7bd016ede627ad3bdb8bc99b9992a18dc8e8898f252", + "blk.2.ffn_down.weight": "f6231d2a25426fbd45b9f1160aa484220eb227ceef0348c4a6a6de890606e5ef", + "blk.2.ffn_down.bias": "429e00556e8dc63a785238b309b9d83738500c1ef6d736fe6526ad88ea496d27", + "blk.2.layer_output_norm.weight": "651457a573adf3f7dd9ee5dfe1c8e89389e94443993aab77ec6a0b05aa621e35", + "blk.2.layer_output_norm.bias": "41fbbeda7fd89b0cef5f945ae44011c316982390401d6f75ba8c6d365e185247", + "blk.3.attn_q.weight": "95a43f32949d2cb8d22815bb27a44abfc6665ba96221af817dfe058cb6ca72c6", + "blk.3.attn_q.bias": "f4e34385e75d8108b6b3bd336106e2133a8c9be0cc343dfe5dc48c32a823c7cb", + "blk.3.attn_k.weight": "6b892da6a17d4d3265265a15f695864a31813ee8c8e710ae9bc9e1adbc6c9a18", + "blk.3.attn_k.bias": "40b8067b641a56014cee42548240aa8930820958b1933004892b5f04fbaef39e", + "blk.3.attn_v.weight": "9fcd5922319dd2a461082a5ce040c1dfe65d87d70ca6547dd0b46eeecc3eeb2b", + "blk.3.attn_v.bias": "b528c56212e66931fdbe267ac327a9c2f87cd03baff3ea719e30afe681da15f1", + "blk.3.attn_output.weight": "e3b178c1b03981e75510e0d277af23ea59cc404b5394e61bd32291825719b502", + "blk.3.attn_output.bias": "712c84d39a6a5a9c06a09da8fd9939ba0d5525524a4bba61ea4de09b48f45cae", + "blk.3.attn_output_norm.weight": "d1ffac88e675592ff72f8a617be32b4a381d443b2f8f2645dbe44a1e5745aac0", + "blk.3.attn_output_norm.bias": "ea31a1c73146234c50e0e43f485c458413714867b8e2703af66482f7db2d6c40", + "blk.3.ffn_up.weight": "4ef4f3b9a1ea6ab2ef2eb6e8b008e06a44790d099d97482a05a51e39a29afac0", + "blk.3.ffn_up.bias": "06a4296dda16f452675c51f108079fe7722552d6521c737d97734943818b9a2b", + "blk.3.ffn_down.weight": "f114b2bebe392c7d80433bb880c6730293aa4561b0b0370dcdaf7472daebd847", + "blk.3.ffn_down.bias": "2c8e67831d28a3bf613fc7912ae3259b63d72abcaf4d30efd8800758400158de", + "blk.3.layer_output_norm.weight": "a1dfeb7b5a51dd56447312ca41e2ad2f361a3ea12ddc355127f5f4219fb0a482", + "blk.3.layer_output_norm.bias": "1ed630021b25c6c6fc93fd32988b9907df966d4982a93081f639aac3044618ab", + "blk.4.attn_q.weight": "b5fae4c1f9a5f33a2a2e816ac0c01c25f422e4efdd59ef1ed93da2610e5370fc", + "blk.4.attn_q.bias": "c2e376524ea98ac3b10d9eee19ecb1b1e261fa5149efe0232844c923dfb428fb", + "blk.4.attn_k.weight": "a4632f5ebf9321d9d08f9112a4e5dda2efe5671df4a4e67fee24845f5b14af16", + "blk.4.attn_k.bias": "a9a02ffb8b8b4f6dfe487a7e0341f1d5318c9d2b793a688f34cb1b22fc66ef60", + "blk.4.attn_v.weight": "10ad8deb81d9fa093b1e5c0f24ea82aa7df43e6aca49e260fcbea56eab8cc86a", + "blk.4.attn_v.bias": "7326813e181e021130bd33ac136293fcffccce2d1d8cb59041e5b13a8cceacf6", + "blk.4.attn_output.weight": "c92573088c7437c2b3cda51490e152c27fb19e5468df591eabba5a49d5398d44", + "blk.4.attn_output.bias": "14e10b419e5859af1eb685af5c330aee67048cd704dcead9217840c6f5393222", + "blk.4.attn_output_norm.weight": "02b6831c0e0fb0edbc579a92812a1dd972cb15d14fcd382d4427c5a7b300ac44", + "blk.4.attn_output_norm.bias": "7eed5cd503bb6bb6ceb1bc8b07cc077903a4f14fb8b9d6cdf39644815ecf1374", + "blk.4.ffn_up.weight": "8d0c91d62e74d6431321116a37cf3339e630bd50ba164d3304fc4fe8dd831223", + "blk.4.ffn_up.bias": "d325f07f73c005a273c484c7be8e7abb4d6e8a5c4fd093f5869133b97629d017", + "blk.4.ffn_down.weight": "7ba7bd81143f40537b84f938e403e19f30e4928625eb371de052b9025beb4d21", + "blk.4.ffn_down.bias": "2853d9c2a75288214a4bf4907dc19d04d01926f4913d302b1aa7bdbfcce0f7a1", + "blk.4.layer_output_norm.weight": "a4ed1885fa77b90fed5300c355ef0aa0c876a8c747151d9d790939d464d57d4f", + "blk.4.layer_output_norm.bias": "62142a81e813a9e636333b2b805d6bc3b17c5e7cd4b15adce1ada6bc9a32563c", + "blk.5.attn_q.weight": "afc1dff080a72c3daad01384b1448d476aaf789871017c8ff8e144788887995d", + "blk.5.attn_q.bias": "748a820371c1d4f872c84545b36358d239c35bf6c99e2812c237d88c3292763b", + "blk.5.attn_k.weight": "59e30c1ed8acd2cbb01de5f62e7804015b9ecf98ba157d98cab016344639eda5", + "blk.5.attn_k.bias": "f839520078f9e589496e982e86d0126c7aa14196047339abffcf49a696229f77", + "blk.5.attn_v.weight": "3e21fb874e21b90308e1f46af034a3c32d3eba1628d62ae5f2246d6af5818923", + "blk.5.attn_v.bias": "5cd4852bf95c1444d10d756750f6bf49f842c0b39e9953c7f408bb67c325ac8c", + "blk.5.attn_output.weight": "636ce6a7752895f204b9d01ba0aedd9a294f908b42f372c22a16d9dd590d7471", + "blk.5.attn_output.bias": "82d924d4b0d2b94f2bbff91619216d6967a3541ce9b1531a6a60457a67b5d219", + "blk.5.attn_output_norm.weight": "5e7bd0a8d3396080f3360d7c4700bf094a06216431bd014c4479eef72ecf4271", + "blk.5.attn_output_norm.bias": "66c6de5edda5466d029c6753780be81ccd4218bf8bc00680000e0f06856ab712", + "blk.5.ffn_up.weight": "5bbf6e7ea380e216e33f8bee06d25f2265359d3876a300e92bc6e41d48e33430", + "blk.5.ffn_up.bias": "9d795388bb36fb33ad3a37fea3ccb4937838e02800a608fb47d363cd06b47370", + "blk.5.ffn_down.weight": "2fd628974e7f075479dd227b46fbd48ae8d3ca34d735b36f391ac06410730368", + "blk.5.ffn_down.bias": "cd213ba9eaa75fa541648097fbe9c96e58077e6c3ad6ad2fb1f21f8350f44291", + "blk.5.layer_output_norm.weight": "159a9df41d15b7022d136f86a2a2631c4635f9816e957472217077b522bcf52a", + "blk.5.layer_output_norm.bias": "24c1f27ffd1eb4e5be7e3a2909943e6f0980635d761fa1efdd0c19645da23766" +} diff --git a/convert/tokenizer.go b/convert/tokenizer.go index 0d42a6d8a..653df6d28 100644 --- a/convert/tokenizer.go +++ b/convert/tokenizer.go @@ -1,7 +1,6 @@ package convert import ( - "cmp" "crypto/sha256" "encoding/hex" "encoding/json" @@ -11,6 +10,8 @@ import ( "log/slog" "os" "slices" + + "golang.org/x/exp/maps" ) const ( @@ -184,32 +185,32 @@ func parseVocabularyFromTokenizer(fsys fs.FS) (*Vocabulary, error) { return nil, err } - var tokens []token + tokens := make(map[int]token, len(t.Model.Vocab)) for k, v := range t.Model.Vocab { - tokens = append(tokens, token{ + tokens[v] = token{ ID: v, Content: k, - }) + } } - for _, t := range t.AddedTokens { - t.UserDefined = true - tokens = append(tokens, t) + for _, token := range t.AddedTokens { + token.UserDefined = true + tokens[token.ID] = token } - slices.SortFunc(tokens, func(i, j token) int { - return cmp.Compare(i.ID, j.ID) - }) + keys := maps.Keys(tokens) + slices.Sort(keys) v := Vocabulary{Model: "gpt2"} - for _, t := range tokens { - v.Tokens = append(v.Tokens, t.Content) - v.Scores = append(v.Scores, float32(t.ID)) + for _, k := range keys { + token := tokens[k] + v.Tokens = append(v.Tokens, token.Content) + v.Scores = append(v.Scores, float32(token.ID)) switch { - case t.Special: + case token.Special: v.Types = append(v.Types, tokenTypeControl) - case t.UserDefined: + case token.UserDefined: v.Types = append(v.Types, tokenTypeUserDefined) default: v.Types = append(v.Types, tokenTypeNormal) From beb49eef65acefc64a6ae0562ce58467e6974fde Mon Sep 17 00:00:00 2001 From: Michael Yang Date: Fri, 7 Jun 2024 14:55:56 -0700 Subject: [PATCH 272/384] create bert models from cli --- cmd/cmd.go | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/cmd/cmd.go b/cmd/cmd.go index fd7246c8d..a8a02605c 100644 --- a/cmd/cmd.go +++ b/cmd/cmd.go @@ -223,6 +223,14 @@ func tempZipFiles(path string) (string, error) { } files = append(files, js...) + // bert models require a nested config.json + // TODO(mxyng): merge this with the glob above + js, err = glob(filepath.Join(path, "**/*.json"), "text/plain") + if err != nil { + return "", err + } + files = append(files, js...) + if tks, _ := glob(filepath.Join(path, "tokenizer.model"), "application/octet-stream"); len(tks) > 0 { // add tokenizer.model if it exists, tokenizer.json is automatically picked up by the previous glob // tokenizer.model might be a unresolved git lfs reference; error if it is @@ -252,6 +260,11 @@ func tempZipFiles(path string) (string, error) { return "", err } + zfi.Name, err = filepath.Rel(path, file) + if err != nil { + return "", err + } + zf, err := zipfile.CreateHeader(zfi) if err != nil { return "", err From 3546bbd08c52df73eb6523b06b13f1b2dfeaa5fb Mon Sep 17 00:00:00 2001 From: Michael Yang Date: Fri, 28 Jun 2024 13:27:05 -0700 Subject: [PATCH 273/384] convert gemma2 --- convert/convert.go | 11 ++++++-- convert/convert_bert.go | 9 +++--- convert/convert_gemma.go | 14 ++++----- convert/convert_gemma2.go | 44 +++++++++++++++++++++++++++++ convert/convert_llama.go | 19 ++++++------- convert/convert_mixtral.go | 9 ++++-- convert/convert_phi3.go | 11 ++++---- convert/convert_test.go | 1 + convert/reader.go | 12 ++++---- convert/reader_safetensors.go | 5 ++-- convert/reader_torch.go | 5 ++-- convert/testdata/gemma-2-9b-it.json | 6 ++++ convert/tokenizer_spm.go | 32 ++++++++++++++++++++- 13 files changed, 132 insertions(+), 46 deletions(-) create mode 100644 convert/convert_gemma2.go create mode 100644 convert/testdata/gemma-2-9b-it.json diff --git a/convert/convert.go b/convert/convert.go index f51e96658..5a314cdd7 100644 --- a/convert/convert.go +++ b/convert/convert.go @@ -7,6 +7,7 @@ import ( "io" "io/fs" "log/slog" + "strings" "github.com/ollama/ollama/llm" ) @@ -58,11 +59,13 @@ type Converter interface { KV(*Tokenizer) llm.KV // Tensors maps input tensors to LLM tensors. Model specific modifications can be done here. Tensors([]Tensor) []llm.Tensor + // Replacements returns a list of string pairs to replace in tensor names. + // See [strings.Replacer](https://pkg.go.dev/strings#Replacer) for details + Replacements() []string - // tensorName returns the LLM tensor name for a specific input name - tensorName(string) string // specialTokenTypes returns any special token types the model uses specialTokenTypes() []string + // writeFile writes the model to the provided io.WriteSeeker writeFile(io.WriteSeeker, llm.KV, []llm.Tensor) error } @@ -97,6 +100,8 @@ func Convert(fsys fs.FS, ws io.WriteSeeker) error { conv = &mixtral{} case "GemmaForCausalLM": conv = &gemma{} + case "Gemma2ForCausalLM": + conv = &gemma2{} case "Phi3ForCausalLM": conv = &phi3{} case "BertModel": @@ -131,7 +136,7 @@ func Convert(fsys fs.FS, ws io.WriteSeeker) error { slog.Debug("vocabulary", "size", len(t.Vocabulary.Tokens)) } - ts, err := parseTensors(fsys) + ts, err := parseTensors(fsys, strings.NewReplacer(conv.Replacements()...)) if err != nil { return err } diff --git a/convert/convert_bert.go b/convert/convert_bert.go index 62fad1472..4547a705b 100644 --- a/convert/convert_bert.go +++ b/convert/convert_bert.go @@ -144,9 +144,8 @@ func (p *bert) Tensors(ts []Tensor) []llm.Tensor { continue } - name := p.tensorName(t.Name()) out = append(out, llm.Tensor{ - Name: name, + Name: t.Name(), Kind: t.Kind(), Shape: t.Shape(), WriterTo: t, @@ -156,8 +155,8 @@ func (p *bert) Tensors(ts []Tensor) []llm.Tensor { return out } -func (bert) tensorName(n string) string { - return strings.NewReplacer( +func (bert) Replacements() []string { + return []string{ "encoder.layer", "blk", "encoder.layers", "blk", "embeddings.word_embeddings", "token_embd", @@ -172,5 +171,5 @@ func (bert) tensorName(n string) string { "intermediate.dense", "ffn_up", "output.dense", "ffn_down", "output.LayerNorm", "layer_output_norm", - ).Replace(n) + } } diff --git a/convert/convert_gemma.go b/convert/convert_gemma.go index 9213e1576..333e4c835 100644 --- a/convert/convert_gemma.go +++ b/convert/convert_gemma.go @@ -44,15 +44,14 @@ func (p *gemma) KV(t *Tokenizer) llm.KV { } func (p *gemma) Tensors(ts []Tensor) []llm.Tensor { - var out []llm.Tensor + out := make([]llm.Tensor, 0, len(ts)) for _, t := range ts { - name := p.tensorName(t.Name()) - if strings.HasSuffix(name, "_norm.weight") { + if strings.HasSuffix(t.Name(), "_norm.weight") { t.SetRepacker(p.addOne) } out = append(out, llm.Tensor{ - Name: name, + Name: t.Name(), Kind: t.Kind(), Shape: t.Shape(), WriterTo: t, @@ -62,8 +61,8 @@ func (p *gemma) Tensors(ts []Tensor) []llm.Tensor { return out } -func (p *gemma) tensorName(n string) string { - return strings.NewReplacer( +func (p *gemma) Replacements() []string { + return []string{ "model.embed_tokens", "token_embd", "model.norm", "output_norm", "model.layers", "blk", @@ -76,8 +75,7 @@ func (p *gemma) tensorName(n string) string { "mlp.down_proj", "ffn_down", "mlp.up_proj", "ffn_up", "post_attention_layernorm", "ffn_norm", - "block_sparse_moe.gate", "ffn_inp", - ).Replace(n) + } } func (*gemma) addOne(_ string, data []float32, shape []uint64) ([]float32, error) { diff --git a/convert/convert_gemma2.go b/convert/convert_gemma2.go new file mode 100644 index 000000000..66be02d6a --- /dev/null +++ b/convert/convert_gemma2.go @@ -0,0 +1,44 @@ +package convert + +import ( + "github.com/ollama/ollama/llm" +) + +type gemma2 struct { + gemma + SlidingWindow uint32 `json:"sliding_window"` + AttentionLogitSoftcap float32 `json:"attn_logit_softcapping"` + FinalLogitSoftcap float32 `json:"final_logit_softcapping"` +} + +func (p *gemma2) KV(t *Tokenizer) llm.KV { + kv := p.Parameters.KV(t) + kv["general.architecture"] = "gemma2" + kv["general.name"] = "gemma2" + kv["gemma2.context_length"] = p.MaxPositionEmbeddings + kv["gemma2.embedding_length"] = p.HiddenSize + kv["gemma2.block_count"] = p.HiddenLayers + kv["gemma2.feed_forward_length"] = p.IntermediateSize + kv["gemma2.attention.head_count"] = p.NumAttentionHeads + kv["gemma2.attention.head_count_kv"] = p.NumKeyValueHeads + kv["gemma2.attention.layer_norm_rms_epsilon"] = p.RMSNormEPS + kv["gemma2.attention.key_length"] = p.HeadDim + kv["gemma2.attention.value_length"] = p.HeadDim + kv["gemma2.attention.sliding_window"] = p.SlidingWindow + kv["gemma2.attn_logit_softcapping"] = p.AttentionLogitSoftcap + kv["gemma2.final_logit_softcapping"] = p.FinalLogitSoftcap + kv["tokenizer.ggml.eot_token_id"] = uint32(107) + kv["tokenizer.ggml.middle_token_id"] = uint32(68) + kv["tokenizer.ggml.prefix_token_id"] = uint32(67) + kv["tokenizer.ggml.suffix_token_id"] = uint32(69) + return kv +} + +func (p *gemma2) Replacements() []string { + return append( + p.gemma.Replacements(), + "post_attention_layernorm", "post_attention_norm", + "pre_feedforward_layernorm", "ffn_norm", + "post_feedforward_layernorm", "post_ffw_norm", + ) +} diff --git a/convert/convert_llama.go b/convert/convert_llama.go index 178b13f33..498d13211 100644 --- a/convert/convert_llama.go +++ b/convert/convert_llama.go @@ -96,14 +96,13 @@ func (p *llama) KV(t *Tokenizer) llm.KV { func (p *llama) Tensors(ts []Tensor) []llm.Tensor { var out []llm.Tensor for _, t := range ts { - name := p.tensorName(t.Name()) - if strings.HasSuffix(name, "attn_q.weight") || - strings.HasSuffix(name, "attn_k.weight") { + if strings.HasSuffix(t.Name(), "attn_q.weight") || + strings.HasSuffix(t.Name(), "attn_k.weight") { t.SetRepacker(p.repack) } out = append(out, llm.Tensor{ - Name: name, + Name: t.Name(), Kind: t.Kind(), Shape: t.Shape(), WriterTo: t, @@ -113,8 +112,8 @@ func (p *llama) Tensors(ts []Tensor) []llm.Tensor { return out } -func (p *llama) tensorName(n string) string { - return strings.NewReplacer( +func (p *llama) Replacements() []string { + return []string{ "lm_head", "output", "model.embed_tokens", "token_embd", "model.norm", "output_norm", @@ -128,9 +127,7 @@ func (p *llama) tensorName(n string) string { "mlp.down_proj", "ffn_down", "mlp.up_proj", "ffn_up", "post_attention_layernorm", "ffn_norm", - // mixtral - "block_sparse_moe.gate", "ffn_gate_inp", - ).Replace(n) + } } func (p *llama) repack(name string, data []float32, shape []uint64) ([]float32, error) { @@ -140,9 +137,9 @@ func (p *llama) repack(name string, data []float32, shape []uint64) ([]float32, } var heads uint32 - if strings.HasSuffix(name, "q_proj.weight") { + if strings.HasSuffix(name, "attn_q.weight") { heads = p.NumAttentionHeads - } else if strings.HasSuffix(name, "k_proj.weight") { + } else if strings.HasSuffix(name, "attn_k.weight") { heads = cmp.Or(p.NumKeyValueHeads, p.NumAttentionHeads) } else { return nil, fmt.Errorf("unknown tensor for repack: %s", name) diff --git a/convert/convert_mixtral.go b/convert/convert_mixtral.go index 3263a27b3..97a86b303 100644 --- a/convert/convert_mixtral.go +++ b/convert/convert_mixtral.go @@ -15,8 +15,6 @@ type mixtral struct { NumExpertsPerToken uint32 `json:"num_experts_per_tok"` } -var _ Converter = (*mixtral)(nil) - func (p *mixtral) KV(t *Tokenizer) llm.KV { kv := p.llama.KV(t) @@ -72,6 +70,13 @@ func (p *mixtral) Tensors(ts []Tensor) []llm.Tensor { return append(out, p.llama.Tensors(ts)...) } +func (p *mixtral) Replacements() []string { + return append( + p.llama.Replacements(), + "block_sparse_moe.gate", "ffn_gate_inp", + ) +} + type experts []Tensor func (e experts) WriteTo(w io.Writer) (int64, error) { diff --git a/convert/convert_phi3.go b/convert/convert_phi3.go index 0f645217d..4ee59ff51 100644 --- a/convert/convert_phi3.go +++ b/convert/convert_phi3.go @@ -74,8 +74,7 @@ func (p *phi3) Tensors(ts []Tensor) []llm.Tensor { out := make([]llm.Tensor, 0, len(ts)+2) for _, t := range ts { - name := p.tensorName(t.Name()) - if strings.HasPrefix(name, "blk.0.") { + if strings.HasPrefix(t.Name(), "blk.0.") { addRopeFactors.Do(func() { out = append(out, llm.Tensor{ Name: "rope_factors_long.weight", @@ -92,7 +91,7 @@ func (p *phi3) Tensors(ts []Tensor) []llm.Tensor { } out = append(out, llm.Tensor{ - Name: name, + Name: t.Name(), Kind: t.Kind(), Shape: t.Shape(), WriterTo: t, @@ -102,8 +101,8 @@ func (p *phi3) Tensors(ts []Tensor) []llm.Tensor { return out } -func (p *phi3) tensorName(n string) string { - return strings.NewReplacer( +func (p *phi3) Replacements() []string { + return []string{ "lm_head", "output", "model.embed_tokens", "token_embd", "model.norm", "output_norm", @@ -114,7 +113,7 @@ func (p *phi3) tensorName(n string) string { "mlp.down_proj", "ffn_down", "mlp.gate_up_proj", "ffn_up", "post_attention_layernorm", "ffn_norm", - ).Replace(n) + } } type ropeFactor []float32 diff --git a/convert/convert_test.go b/convert/convert_test.go index e3ab00982..e78afab7a 100644 --- a/convert/convert_test.go +++ b/convert/convert_test.go @@ -68,6 +68,7 @@ func TestConvertFull(t *testing.T) { // microsoft/Phi-3-mini-128-instruct@d548c233192db00165d842bf8edff054bb3212f8 "Phi-3-mini-128k-instruct", "all-MiniLM-L6-v2", + "gemma-2-9b-it", } for i := range cases { diff --git a/convert/reader.go b/convert/reader.go index 294a7c40b..5bba0406b 100644 --- a/convert/reader.go +++ b/convert/reader.go @@ -35,9 +35,9 @@ const ( ) func (t tensorBase) Kind() uint32 { - if strings.HasSuffix(t.name, ".block_sparse_moe.gate.weight") { - return 0 - } else if t.name == "embeddings.token_type_embeddings.weight" { + if strings.HasSuffix(t.name, ".ffn_gate_inp.weight") || + t.name == "token_types.weight" { + // these tensors are always F32 return 0 } @@ -57,10 +57,10 @@ func (t *tensorBase) SetRepacker(fn repacker) { type repacker func(string, []float32, []uint64) ([]float32, error) -func parseTensors(fsys fs.FS) ([]Tensor, error) { +func parseTensors(fsys fs.FS, replacer *strings.Replacer) ([]Tensor, error) { patterns := []struct { Pattern string - Func func(fs.FS, ...string) ([]Tensor, error) + Func func(fs.FS, *strings.Replacer, ...string) ([]Tensor, error) }{ {"model-*-of-*.safetensors", parseSafetensors}, {"model.safetensors", parseSafetensors}, @@ -76,7 +76,7 @@ func parseTensors(fsys fs.FS) ([]Tensor, error) { } if len(matches) > 0 { - return pattern.Func(fsys, matches...) + return pattern.Func(fsys, replacer, matches...) } } diff --git a/convert/reader_safetensors.go b/convert/reader_safetensors.go index 42f902a5a..32a362cd9 100644 --- a/convert/reader_safetensors.go +++ b/convert/reader_safetensors.go @@ -8,6 +8,7 @@ import ( "io" "io/fs" "slices" + "strings" "github.com/d4l3k/go-bfloat16" "github.com/x448/float16" @@ -20,7 +21,7 @@ type safetensorMetadata struct { Offsets []int64 `json:"data_offsets"` } -func parseSafetensors(fsys fs.FS, ps ...string) ([]Tensor, error) { +func parseSafetensors(fsys fs.FS, replacer *strings.Replacer, ps ...string) ([]Tensor, error) { var ts []Tensor for _, p := range ps { f, err := fsys.Open(p) @@ -56,7 +57,7 @@ func parseSafetensors(fsys fs.FS, ps ...string) ([]Tensor, error) { offset: safetensorsPad(n, value.Offsets[0]), size: safetensorsPad(n, value.Offsets[1]) - safetensorsPad(n, value.Offsets[0]), tensorBase: &tensorBase{ - name: key, + name: replacer.Replace(key), shape: value.Shape, }, }) diff --git a/convert/reader_torch.go b/convert/reader_torch.go index 531996bf2..1b3e1c9f1 100644 --- a/convert/reader_torch.go +++ b/convert/reader_torch.go @@ -3,12 +3,13 @@ package convert import ( "io" "io/fs" + "strings" "github.com/nlpodyssey/gopickle/pytorch" "github.com/nlpodyssey/gopickle/types" ) -func parseTorch(fsys fs.FS, ps ...string) ([]Tensor, error) { +func parseTorch(fsys fs.FS, replacer *strings.Replacer, ps ...string) ([]Tensor, error) { var ts []Tensor for _, p := range ps { pt, err := pytorch.Load(p) @@ -27,7 +28,7 @@ func parseTorch(fsys fs.FS, ps ...string) ([]Tensor, error) { ts = append(ts, torch{ storage: t.(*pytorch.Tensor).Source, tensorBase: &tensorBase{ - name: k.(string), + name: replacer.Replace(k.(string)), shape: shape, }, }) diff --git a/convert/testdata/gemma-2-9b-it.json b/convert/testdata/gemma-2-9b-it.json new file mode 100644 index 000000000..90cdbee47 --- /dev/null +++ b/convert/testdata/gemma-2-9b-it.json @@ -0,0 +1,6 @@ +{ + "general.architecture": "gemma2", + "gemma2.attention.sliding_window": "4096", + "gemma2.attn_logit_softcapping": "50", + "gemma2.final_logit_softcapping": "30" +} diff --git a/convert/tokenizer_spm.go b/convert/tokenizer_spm.go index babf702c7..5e506087c 100644 --- a/convert/tokenizer_spm.go +++ b/convert/tokenizer_spm.go @@ -15,6 +15,11 @@ import ( ) func parseSentencePiece(fsys fs.FS) (*Vocabulary, error) { + ast, err := parseAdditionalSpecialTokens(fsys) + if err != nil { + return nil, err + } + bts, err := fs.ReadFile(fsys, "tokenizer.model") if err != nil { return nil, err @@ -37,7 +42,12 @@ func parseSentencePiece(fsys fs.FS) (*Vocabulary, error) { sentencepiece.ModelProto_SentencePiece_BYTE: v.Types = append(v.Types, int32(t)) default: - v.Types = append(v.Types, int32(sentencepiece.ModelProto_SentencePiece_NORMAL)) + tt := int32(sentencepiece.ModelProto_SentencePiece_NORMAL) + if slices.Contains(ast, piece.GetPiece()) { + tt = int32(sentencepiece.ModelProto_SentencePiece_CONTROL) + } + + v.Types = append(v.Types, tt) } } @@ -81,3 +91,23 @@ func parseSentencePiece(fsys fs.FS) (*Vocabulary, error) { return &v, nil } + +func parseAdditionalSpecialTokens(fsys fs.FS) ([]string, error) { + f, err := fsys.Open("special_tokens_map.json") + if errors.Is(err, os.ErrNotExist) { + return nil, nil + } else if err != nil { + return nil, err + } + defer f.Close() + + var m struct { + AdditionalSpecialTokens []string `json:"additional_special_tokens"` + } + + if err := json.NewDecoder(f).Decode(&m); err != nil { + return nil, err + } + + return m.AdditionalSpecialTokens, nil +} From 77903ab8b4fb8075faad7bde5bde2eee3173e407 Mon Sep 17 00:00:00 2001 From: Michael Yang Date: Mon, 29 Jul 2024 14:53:02 -0700 Subject: [PATCH 274/384] llama3.1 --- convert/convert_bert.go | 1 - convert/convert_gemma.go | 1 - convert/convert_gemma2.go | 1 - convert/convert_llama.go | 43 +++++++++++++++++-- convert/convert_phi3.go | 1 - convert/convert_test.go | 1 + .../testdata/Meta-Llama-3.1-8B-Instruct.json | 3 ++ llm/memory_test.go | 1 - server/sched_test.go | 1 - 9 files changed, 44 insertions(+), 9 deletions(-) create mode 100644 convert/testdata/Meta-Llama-3.1-8B-Instruct.json diff --git a/convert/convert_bert.go b/convert/convert_bert.go index 4547a705b..6e7d59fec 100644 --- a/convert/convert_bert.go +++ b/convert/convert_bert.go @@ -88,7 +88,6 @@ func (p *bert) parseMore(fsys fs.FS) error { func (p *bert) KV(t *Tokenizer) llm.KV { kv := p.Parameters.KV(t) kv["general.architecture"] = "bert" - kv["general.name"] = "bert" kv["bert.attention.causal"] = false kv["bert.pooling_type"] = p.PoolingType diff --git a/convert/convert_gemma.go b/convert/convert_gemma.go index 333e4c835..c43168087 100644 --- a/convert/convert_gemma.go +++ b/convert/convert_gemma.go @@ -26,7 +26,6 @@ var _ Converter = (*gemma)(nil) func (p *gemma) KV(t *Tokenizer) llm.KV { kv := p.Parameters.KV(t) kv["general.architecture"] = "gemma" - kv["general.name"] = "gemma" kv["gemma.context_length"] = p.MaxPositionEmbeddings kv["gemma.embedding_length"] = p.HiddenSize kv["gemma.block_count"] = p.HiddenLayers diff --git a/convert/convert_gemma2.go b/convert/convert_gemma2.go index 66be02d6a..084f9c529 100644 --- a/convert/convert_gemma2.go +++ b/convert/convert_gemma2.go @@ -14,7 +14,6 @@ type gemma2 struct { func (p *gemma2) KV(t *Tokenizer) llm.KV { kv := p.Parameters.KV(t) kv["general.architecture"] = "gemma2" - kv["general.name"] = "gemma2" kv["gemma2.context_length"] = p.MaxPositionEmbeddings kv["gemma2.embedding_length"] = p.HiddenSize kv["gemma2.block_count"] = p.HiddenLayers diff --git a/convert/convert_llama.go b/convert/convert_llama.go index 498d13211..27f924fbf 100644 --- a/convert/convert_llama.go +++ b/convert/convert_llama.go @@ -3,6 +3,7 @@ package convert import ( "cmp" "fmt" + "math" "strings" "github.com/pdevine/tensor" @@ -27,8 +28,14 @@ type llama struct { NumKeyValueHeads uint32 `json:"num_key_value_heads"` RopeTheta float32 `json:"rope_theta"` RopeScaling struct { - Type string `json:"type"` - Factor float32 `json:"factor"` + Type string `json:"type"` + RopeType string `json:"rope_type"` + Factor float32 `json:"factor"` + LowFrequencyFactor float32 `json:"low_freq_factor"` + HighFrequencyFactor float32 `json:"high_freq_factor"` + OriginalMaxPositionalEmbeddings uint32 `json:"original_max_positional_embeddings"` + + factors ropeFactor } `json:"rope_scaling"` RMSNormEPS float32 `json:"rms_norm_eps"` LayerNormEPS float32 `json:"layer_norm_eps"` @@ -42,7 +49,6 @@ var _ Converter = (*llama)(nil) func (p *llama) KV(t *Tokenizer) llm.KV { kv := p.Parameters.KV(t) kv["general.architecture"] = "llama" - kv["general.name"] = "llama" kv["llama.vocab_size"] = p.VocabSize kv["llama.block_count"] = cmp.Or(p.NLayers, p.NumHiddenLayers, p.NLayer) @@ -71,6 +77,27 @@ func (p *llama) KV(t *Tokenizer) llm.KV { if p.RopeScaling.Type == "linear" { kv["llama.rope.scaling.type"] = p.RopeScaling.Type kv["llama.rope.scaling.factor"] = p.RopeScaling.Factor + } else if p.RopeScaling.RopeType == "llama3" { + dim := p.HiddenSize / p.NumAttentionHeads + for i := uint32(0); i < dim; i += 2 { + factor := cmp.Or(p.RopeScaling.Factor, 8.0) + factorLow := cmp.Or(p.RopeScaling.LowFrequencyFactor, 1.0) + factorHigh := cmp.Or(p.RopeScaling.HighFrequencyFactor, 4.0) + + original := cmp.Or(p.RopeScaling.OriginalMaxPositionalEmbeddings, 8192) + lambdaLow := float32(original) / factorLow + lambdaHigh := float32(original) / factorHigh + + lambda := 2 * math.Pi * math.Pow(float64(p.RopeTheta), float64(i)/float64(dim)) + if lambda < float64(lambdaHigh) { + p.RopeScaling.factors = append(p.RopeScaling.factors, 1.0) + } else if lambda > float64(lambdaLow) { + p.RopeScaling.factors = append(p.RopeScaling.factors, factor) + } else { + smooth := (float32(original)/float32(lambda) - factorLow) / (factorHigh - factorLow) + p.RopeScaling.factors = append(p.RopeScaling.factors, 1.0/((1-smooth)/factor+smooth)) + } + } } if p.NumKeyValueHeads > 0 { @@ -95,6 +122,16 @@ func (p *llama) KV(t *Tokenizer) llm.KV { func (p *llama) Tensors(ts []Tensor) []llm.Tensor { var out []llm.Tensor + + if p.RopeScaling.factors != nil { + out = append(out, llm.Tensor{ + Name: "rope_freqs.weight", + Kind: 0, + Shape: []uint64{uint64(len(p.RopeScaling.factors))}, + WriterTo: p.RopeScaling.factors, + }) + } + for _, t := range ts { if strings.HasSuffix(t.Name(), "attn_q.weight") || strings.HasSuffix(t.Name(), "attn_k.weight") { diff --git a/convert/convert_phi3.go b/convert/convert_phi3.go index 4ee59ff51..64d3d012e 100644 --- a/convert/convert_phi3.go +++ b/convert/convert_phi3.go @@ -40,7 +40,6 @@ var _ Converter = (*phi3)(nil) func (p *phi3) KV(t *Tokenizer) llm.KV { kv := p.Parameters.KV(t) kv["general.architecture"] = "phi3" - kv["general.name"] = "phi3" kv["phi3.context_length"] = p.MaxPositionEmbeddings kv["phi3.embedding_length"] = cmp.Or(p.HiddenSize, p.NEmbd) kv["phi3.feed_forward_length"] = p.IntermediateSize diff --git a/convert/convert_test.go b/convert/convert_test.go index e78afab7a..64b7df3b3 100644 --- a/convert/convert_test.go +++ b/convert/convert_test.go @@ -62,6 +62,7 @@ func TestMain(m *testing.M) { func TestConvertFull(t *testing.T) { cases := []string{ "Meta-Llama-3-8B-Instruct", + "Meta-Llama-3.1-8B-Instruct", "Mistral-7B-Instruct-v0.2", "Mixtral-8x7B-Instruct-v0.1", "gemma-2b-it", diff --git a/convert/testdata/Meta-Llama-3.1-8B-Instruct.json b/convert/testdata/Meta-Llama-3.1-8B-Instruct.json new file mode 100644 index 000000000..ad7cd20ac --- /dev/null +++ b/convert/testdata/Meta-Llama-3.1-8B-Instruct.json @@ -0,0 +1,3 @@ +{ + "rope_freqs.weight": "80fd5efb2f729381785b293a091a268cfeceb0079167f6ece9b07070e662b222" +} diff --git a/llm/memory_test.go b/llm/memory_test.go index 6cf0119f9..ffb14286b 100644 --- a/llm/memory_test.go +++ b/llm/memory_test.go @@ -33,7 +33,6 @@ func TestEstimateGPULayers(t *testing.T) { assert.Len(t, tensors, inputLayerCount+1) err = WriteGGUF(f, KV{ "general.architecture": "llama", - "general.name": "name", "llama.context_length": uint32(32), "llama.embedding_length": uint32(4096), "llama.block_count": uint32(inputLayerCount), diff --git a/server/sched_test.go b/server/sched_test.go index 713b9259e..fb049574f 100644 --- a/server/sched_test.go +++ b/server/sched_test.go @@ -117,7 +117,6 @@ func newScenarioRequest(t *testing.T, ctx context.Context, modelName string, est require.NoError(t, llm.WriteGGUF(f, llm.KV{ "general.architecture": "llama", - "general.name": "name", "llama.context_length": uint32(32), "llama.embedding_length": uint32(4096), "llama.block_count": uint32(1), From 90ca84172c2a98ecfd76eb7e05cd3e33e1dde507 Mon Sep 17 00:00:00 2001 From: Daniel Hiltgen Date: Thu, 22 Aug 2024 14:51:42 -0700 Subject: [PATCH 275/384] Fix embeddings memory corruption (#6467) * Fix embeddings memory corruption The patch was leading to a buffer overrun corruption. Once removed though, parallism in server.cpp lead to hitting an assert due to slot/seq IDs being >= token count. To work around this, only use slot 0 for embeddings. * Fix embed integration test assumption The token eval count has changed with recent llama.cpp bumps (0.3.5+) --- integration/embed_test.go | 8 ++--- llm/ext_server/server.cpp | 8 ++++- llm/patches/08-pooling.diff | 60 ------------------------------------- server/sched.go | 5 ++++ 4 files changed, 16 insertions(+), 65 deletions(-) delete mode 100644 llm/patches/08-pooling.diff diff --git a/integration/embed_test.go b/integration/embed_test.go index 10333d5df..4a68af68a 100644 --- a/integration/embed_test.go +++ b/integration/embed_test.go @@ -70,8 +70,8 @@ func TestAllMiniLMEmbed(t *testing.T) { t.Fatalf("expected 0.010071031, got %.8f", res.Embeddings[0][0]) } - if res.PromptEvalCount != 8 { - t.Fatalf("expected 8 prompt tokens, got %d", res.PromptEvalCount) + if res.PromptEvalCount != 6 { + t.Fatalf("expected 6 prompt tokens, got %d", res.PromptEvalCount) } } @@ -102,8 +102,8 @@ func TestAllMiniLMBatchEmbed(t *testing.T) { t.Fatalf("expected 0.010071031 and -0.009802706, got %.8f and %.8f", res.Embeddings[0][0], res.Embeddings[1][0]) } - if res.PromptEvalCount != 16 { - t.Fatalf("expected 16 prompt tokens, got %d", res.PromptEvalCount) + if res.PromptEvalCount != 12 { + t.Fatalf("expected 12 prompt tokens, got %d", res.PromptEvalCount) } } diff --git a/llm/ext_server/server.cpp b/llm/ext_server/server.cpp index 5717c17a9..8e08b850f 100644 --- a/llm/ext_server/server.cpp +++ b/llm/ext_server/server.cpp @@ -1429,7 +1429,13 @@ struct llama_server_context switch (task.type) { case TASK_TYPE_COMPLETION: { - server_slot *slot = prefix_slot(task.data["prompt"]); + server_slot *slot = nullptr; + if (task.embedding_mode) { + // Embedding seq_id (aka slot id) must always be <= token length, so always use slot 0 + slot = slots[0].available() ? &slots[0] : nullptr; + } else { + slot = prefix_slot(task.data["prompt"]); + } if (slot == nullptr) { // if no slot is available, we defer this task for processing later diff --git a/llm/patches/08-pooling.diff b/llm/patches/08-pooling.diff deleted file mode 100644 index 2e4fe11ee..000000000 --- a/llm/patches/08-pooling.diff +++ /dev/null @@ -1,60 +0,0 @@ -diff --git a/src/llama.cpp b/src/llama.cpp -index 721b8f4e..cfe7ac40 100644 ---- a/src/llama.cpp -+++ b/src/llama.cpp -@@ -8420,14 +8420,14 @@ struct llm_build_context { - } - - struct ggml_tensor * build_inp_mean() { -- lctx.inp_mean = ggml_new_tensor_2d(ctx0, GGML_TYPE_F32, n_tokens, n_tokens); -+ lctx.inp_mean = ggml_new_tensor_2d(ctx0, GGML_TYPE_F32, n_tokens, cparams.n_seq_max); - cb(lctx.inp_mean, "inp_mean", -1); - ggml_set_input(lctx.inp_mean); - return lctx.inp_mean; - } - - struct ggml_tensor * build_inp_cls() { -- lctx.inp_cls = ggml_new_tensor_1d(ctx0, GGML_TYPE_I32, n_tokens); -+ lctx.inp_cls = ggml_new_tensor_1d(ctx0, GGML_TYPE_I32, cparams.n_seq_max); - cb(lctx.inp_cls, "inp_cls", -1); - ggml_set_input(lctx.inp_cls); - return lctx.inp_cls; -@@ -13847,19 +13847,16 @@ static void llama_set_inputs(llama_context & lctx, const llama_batch & batch) { - GGML_ASSERT(ggml_backend_buffer_is_host(lctx.inp_mean->buffer)); - - float * data = (float *) lctx.inp_mean->data; -- memset(lctx.inp_mean->data, 0, n_tokens * n_tokens * ggml_element_size(lctx.inp_mean)); -+ memset(lctx.inp_mean->data, 0, n_tokens * cparams.n_seq_max * ggml_element_size(lctx.inp_mean)); - - std::vector sum(n_tokens, 0); - for (int i = 0; i < n_tokens; ++i) { - const llama_seq_id seq_id = batch.seq_id[i][0]; -- -- GGML_ASSERT(seq_id < n_tokens && "seq_id cannot be larger than n_tokens with pooling_type == MEAN"); -- - sum[seq_id] += 1; - } - -- std::vector div(n_tokens, 0.0f); -- for (int i = 0; i < n_tokens; ++i) { -+ std::vector div(cparams.n_seq_max, 0.0f); -+ for (uint32_t i = 0; i < cparams.n_seq_max; ++i) { - const uint64_t s = sum[i]; - if (s > 0) { - div[i] = 1.0f/float(s); -@@ -13879,14 +13876,11 @@ static void llama_set_inputs(llama_context & lctx, const llama_batch & batch) { - GGML_ASSERT(ggml_backend_buffer_is_host(lctx.inp_cls->buffer)); - - uint32_t * data = (uint32_t *) lctx.inp_cls->data; -- memset(lctx.inp_cls->data, 0, n_tokens * ggml_element_size(lctx.inp_cls)); -+ memset(lctx.inp_cls->data, 0, cparams.n_seq_max * ggml_element_size(lctx.inp_cls)); - - for (int i = 0; i < n_tokens; ++i) { - const llama_seq_id seq_id = batch.seq_id[i][0]; - const llama_pos pos = batch.pos[i]; -- -- GGML_ASSERT(seq_id < n_tokens && "seq_id cannot be larger than n_tokens with pooling_type == CLS"); -- - if (pos == 0) { - data[seq_id] = i; - } diff --git a/server/sched.go b/server/sched.go index 9d8c41446..58071bf03 100644 --- a/server/sched.go +++ b/server/sched.go @@ -193,6 +193,11 @@ func (s *Scheduler) processPending(ctx context.Context) { break } + // Embedding models should always be loaded with parallel=1 + if pending.model.CheckCapabilities(CapabilityCompletion) != nil { + numParallel = 1 + } + // Evaluate if the model will fit in the available system memory, or if we should unload a model first if len(gpus) == 1 && gpus[0].Library == "cpu" { // simplifying assumption of defaultParallel when in CPU mode From 0b03b9c32f483be2d7a4e902d13a909b546ae6bf Mon Sep 17 00:00:00 2001 From: Daniel Hiltgen Date: Fri, 23 Aug 2024 11:20:39 -0700 Subject: [PATCH 276/384] llm: Align cmake define for cuda no peer copy (#6455) Define changed recently and this slipped through the cracks with the old name. --- llm/generate/gen_linux.sh | 2 +- llm/generate/gen_windows.ps1 | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/llm/generate/gen_linux.sh b/llm/generate/gen_linux.sh index 6927dda8b..1f702ca25 100755 --- a/llm/generate/gen_linux.sh +++ b/llm/generate/gen_linux.sh @@ -252,7 +252,7 @@ if [ -z "${OLLAMA_SKIP_ROCM_GENERATE}" -a -d "${ROCM_PATH}" ]; then ROCM_VARIANT=_v$(ls ${ROCM_PATH}/lib/librocblas.so.*.*.????? | cut -f5 -d. || true) fi init_vars - CMAKE_DEFS="${COMMON_CMAKE_DEFS} ${CMAKE_DEFS} -DGGML_HIPBLAS=on -DLLAMA_CUDA_NO_PEER_COPY=on -DCMAKE_C_COMPILER=$ROCM_PATH/llvm/bin/clang -DCMAKE_CXX_COMPILER=$ROCM_PATH/llvm/bin/clang++ -DAMDGPU_TARGETS=$(amdGPUs) -DGPU_TARGETS=$(amdGPUs)" + CMAKE_DEFS="${COMMON_CMAKE_DEFS} ${CMAKE_DEFS} -DGGML_HIPBLAS=on -DGGML_CUDA_NO_PEER_COPY=on -DCMAKE_C_COMPILER=$ROCM_PATH/llvm/bin/clang -DCMAKE_CXX_COMPILER=$ROCM_PATH/llvm/bin/clang++ -DAMDGPU_TARGETS=$(amdGPUs) -DGPU_TARGETS=$(amdGPUs)" # Users building from source can tune the exact flags we pass to cmake for configuring llama.cpp if [ -n "${OLLAMA_CUSTOM_ROCM_DEFS}" ]; then echo "OLLAMA_CUSTOM_ROCM_DEFS=\"${OLLAMA_CUSTOM_ROCM_DEFS}\"" diff --git a/llm/generate/gen_windows.ps1 b/llm/generate/gen_windows.ps1 index cbdfd09f1..7179c1bce 100644 --- a/llm/generate/gen_windows.ps1 +++ b/llm/generate/gen_windows.ps1 @@ -355,7 +355,7 @@ function build_rocm() { "-DCMAKE_C_COMPILER=clang.exe", "-DCMAKE_CXX_COMPILER=clang++.exe", "-DGGML_HIPBLAS=on", - "-DLLAMA_CUDA_NO_PEER_COPY=on", + "-DGGML_CUDA_NO_PEER_COPY=on", "-DHIP_PLATFORM=amd", "-DGGML_AVX=on", "-DGGML_AVX2=off", From 7a1e1c1cafe4d3f3f935dc7192f9e66d4b2185b3 Mon Sep 17 00:00:00 2001 From: Daniel Hiltgen Date: Fri, 23 Aug 2024 11:21:12 -0700 Subject: [PATCH 277/384] gpu: Ensure driver version set before variant (#6480) During rebasing, the ordering was inverted causing the cuda version selection logic to break, with driver version being evaluated as zero incorrectly causing a downgrade to v11. --- gpu/gpu.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gpu/gpu.go b/gpu/gpu.go index 72d237a6c..10afb1e3f 100644 --- a/gpu/gpu.go +++ b/gpu/gpu.go @@ -264,6 +264,8 @@ func GetGPUInfo() GpuInfoList { gpuInfo.computeMajor = int(memInfo.major) gpuInfo.computeMinor = int(memInfo.minor) gpuInfo.MinimumMemory = cudaMinimumMemory + gpuInfo.DriverMajor = driverMajor + gpuInfo.DriverMinor = driverMinor variant := cudaVariant(gpuInfo) if depPath != "" { gpuInfo.DependencyPath = depPath @@ -275,8 +277,6 @@ func GetGPUInfo() GpuInfoList { } } gpuInfo.Name = C.GoString(&memInfo.gpu_name[0]) - gpuInfo.DriverMajor = driverMajor - gpuInfo.DriverMinor = driverMinor gpuInfo.Variant = variant // query the management library as well so we can record any skew between the two From 0c819e167becd7f08312d2a1a1e2ac8e8ea5d4da Mon Sep 17 00:00:00 2001 From: Patrick Devine Date: Fri, 23 Aug 2024 11:29:56 -0700 Subject: [PATCH 278/384] convert safetensor adapters into GGUF (#6327) --- cmd/cmd.go | 6 + convert/convert.go | 111 +++++++++++-- convert/convert_bert.go | 18 +- convert/convert_gemma.go | 18 +- convert/convert_gemma2.go | 12 +- convert/convert_gemma2_adapter.go | 91 +++++++++++ convert/convert_llama.go | 16 +- convert/convert_llama_adapter.go | 169 +++++++++++++++++++ convert/convert_mixtral.go | 16 +- convert/convert_phi3.go | 14 +- convert/convert_test.go | 262 +++++++++++++++++++++++++++--- convert/reader.go | 2 + llm/ggml.go | 8 + server/images.go | 11 +- server/model.go | 38 ++++- server/model_test.go | 6 +- 16 files changed, 697 insertions(+), 101 deletions(-) create mode 100644 convert/convert_gemma2_adapter.go create mode 100644 convert/convert_llama_adapter.go diff --git a/cmd/cmd.go b/cmd/cmd.go index a8a02605c..b75c0b5ec 100644 --- a/cmd/cmd.go +++ b/cmd/cmd.go @@ -204,6 +204,12 @@ func tempZipFiles(path string) (string, error) { // safetensors files might be unresolved git lfs references; skip if they are // covers model-x-of-y.safetensors, model.fp32-x-of-y.safetensors, model.safetensors files = append(files, st...) + } else if st, _ := glob(filepath.Join(path, "adapters.safetensors"), "application/octet-stream"); len(st) > 0 { + // covers adapters.safetensors + files = append(files, st...) + } else if st, _ := glob(filepath.Join(path, "adapter_model.safetensors"), "application/octet-stream"); len(st) > 0 { + // covers adapter_model.safetensors + files = append(files, st...) } else if pt, _ := glob(filepath.Join(path, "pytorch_model*.bin"), "application/zip"); len(pt) > 0 { // pytorch files might also be unresolved git lfs references; skip if they are // covers pytorch_model-x-of-y.bin, pytorch_model.fp32-x-of-y.bin, pytorch_model.bin diff --git a/convert/convert.go b/convert/convert.go index 5a314cdd7..8c7b0943a 100644 --- a/convert/convert.go +++ b/convert/convert.go @@ -12,12 +12,22 @@ import ( "github.com/ollama/ollama/llm" ) -type Parameters struct { +type ModelParameters struct { Architectures []string `json:"architectures"` VocabSize uint32 `json:"vocab_size"` } -func (Parameters) KV(t *Tokenizer) llm.KV { +type AdapterParameters struct { + Alpha uint32 `json:"lora_alpha"` + LoraLayers uint32 `json:"lora_layers"` + LoraParameters struct { + Rank uint32 `json:"rank"` + Alpha float32 `json:"alpha"` + Scale float32 `json:"scale"` + } `json:"lora_parameters"` +} + +func (ModelParameters) KV(t *Tokenizer) llm.KV { kv := llm.KV{ "general.file_type": uint32(1), "general.quantization_version": uint32(2), @@ -44,17 +54,40 @@ func (Parameters) KV(t *Tokenizer) llm.KV { return kv } -func (Parameters) specialTokenTypes() []string { +func (p AdapterParameters) KV() llm.KV { + var alpha float32 + if p.LoraParameters.Alpha == 0 { + alpha = float32(p.Alpha) + } else { + alpha = p.LoraParameters.Alpha + } + + kv := llm.KV{ + "adapter.lora.alpha": alpha, + "adapter.type": "lora", + "general.file_type": uint32(1), + "general.type": "adapter", + "general.version": "v0.2", + } + + return kv +} + +func (ModelParameters) specialTokenTypes() []string { return []string{ "bos", "eos", "unk", "sep", "pad", "cls", "mask", } } -func (Parameters) writeFile(ws io.WriteSeeker, kv llm.KV, ts []llm.Tensor) error { +func (ModelParameters) writeFile(ws io.WriteSeeker, kv llm.KV, ts []llm.Tensor) error { return llm.WriteGGUF(ws, kv, ts) } -type Converter interface { +func (AdapterParameters) writeFile(ws io.WriteSeeker, kv llm.KV, ts []llm.Tensor) error { + return llm.WriteGGUF(ws, kv, ts) +} + +type ModelConverter interface { // KV maps parameters to LLM key-values KV(*Tokenizer) llm.KV // Tensors maps input tensors to LLM tensors. Model specific modifications can be done here. @@ -73,17 +106,67 @@ type moreParser interface { parseMore(fs.FS) error } +type AdapterConverter interface { + // KV maps parameters to LLM key-values + KV(llm.KV) llm.KV + // Tensors maps input tensors to LLM tensors. Adapter specific modifications can be done here. + Tensors([]Tensor) []llm.Tensor + // Replacements returns a list of string pairs to replace in tensor names. + // See [strings.Replacer](https://pkg.go.dev/strings#Replacer) for details + Replacements() []string + + writeFile(io.WriteSeeker, llm.KV, []llm.Tensor) error +} + +func ConvertAdapter(fsys fs.FS, ws io.WriteSeeker, baseKV llm.KV) error { + bts, err := fs.ReadFile(fsys, "adapter_config.json") + if err != nil { + return err + } + + var p AdapterParameters + if err := json.Unmarshal(bts, &p); err != nil { + return err + } + + arch, ok := baseKV["general.architecture"] + if !ok { + return errors.New("architecture not set for the base model") + } + + var conv AdapterConverter + switch arch { + case "llama": + conv = &llamaAdapter{} + case "gemma2": + conv = &gemma2Adapter{} + default: + return errors.New("unsupported architecture") + } + + ts, err := parseTensors(fsys, strings.NewReplacer(conv.Replacements()...)) + if err != nil { + return err + } + + if err := json.Unmarshal(bts, conv); err != nil { + return err + } + + return conv.writeFile(ws, conv.KV(baseKV), conv.Tensors(ts)) +} + // Convert writes an Ollama compatible model to the provided io.WriteSeeker based on configurations // and files it finds in the input path. // Supported input model formats include safetensors. // Supported input tokenizers files include tokenizer.json (preferred) and tokenizer.model. -func Convert(fsys fs.FS, ws io.WriteSeeker) error { +func ConvertModel(fsys fs.FS, ws io.WriteSeeker) error { bts, err := fs.ReadFile(fsys, "config.json") if err != nil { return err } - var p Parameters + var p ModelParameters if err := json.Unmarshal(bts, &p); err != nil { return err } @@ -92,20 +175,20 @@ func Convert(fsys fs.FS, ws io.WriteSeeker) error { return errors.New("unknown architecture") } - var conv Converter + var conv ModelConverter switch p.Architectures[0] { case "LlamaForCausalLM", "MistralForCausalLM": - conv = &llama{} + conv = &llamaModel{} case "MixtralForCausalLM": - conv = &mixtral{} + conv = &mixtralModel{} case "GemmaForCausalLM": - conv = &gemma{} + conv = &gemmaModel{} case "Gemma2ForCausalLM": - conv = &gemma2{} + conv = &gemma2Model{} case "Phi3ForCausalLM": - conv = &phi3{} + conv = &phi3Model{} case "BertModel": - conv = &bert{} + conv = &bertModel{} default: return errors.New("unsupported architecture") } diff --git a/convert/convert_bert.go b/convert/convert_bert.go index 6e7d59fec..ea5facaa5 100644 --- a/convert/convert_bert.go +++ b/convert/convert_bert.go @@ -11,8 +11,8 @@ import ( "github.com/ollama/ollama/llm" ) -type bert struct { - Parameters +type bertModel struct { + ModelParameters NLayers uint32 `json:"n_layers"` NumHiddenLayers uint32 `json:"num_hidden_layers"` NLayer uint32 `json:"n_layer"` @@ -33,11 +33,11 @@ type bert struct { } var ( - _ Converter = (*bert)(nil) - _ moreParser = (*bert)(nil) + _ ModelConverter = (*bertModel)(nil) + _ moreParser = (*bertModel)(nil) ) -func (p *bert) parseMore(fsys fs.FS) error { +func (p *bertModel) parseMore(fsys fs.FS) error { bts, err := fs.ReadFile(fsys, "modules.json") if err != nil { return err @@ -85,8 +85,8 @@ func (p *bert) parseMore(fsys fs.FS) error { return nil } -func (p *bert) KV(t *Tokenizer) llm.KV { - kv := p.Parameters.KV(t) +func (p *bertModel) KV(t *Tokenizer) llm.KV { + kv := p.ModelParameters.KV(t) kv["general.architecture"] = "bert" kv["bert.attention.causal"] = false kv["bert.pooling_type"] = p.PoolingType @@ -132,7 +132,7 @@ func (p *bert) KV(t *Tokenizer) llm.KV { return kv } -func (p *bert) Tensors(ts []Tensor) []llm.Tensor { +func (p *bertModel) Tensors(ts []Tensor) []llm.Tensor { var out []llm.Tensor for _, t := range ts { if slices.Contains([]string{ @@ -154,7 +154,7 @@ func (p *bert) Tensors(ts []Tensor) []llm.Tensor { return out } -func (bert) Replacements() []string { +func (bertModel) Replacements() []string { return []string{ "encoder.layer", "blk", "encoder.layers", "blk", diff --git a/convert/convert_gemma.go b/convert/convert_gemma.go index c43168087..b88652947 100644 --- a/convert/convert_gemma.go +++ b/convert/convert_gemma.go @@ -9,8 +9,8 @@ import ( "github.com/ollama/ollama/llm" ) -type gemma struct { - Parameters +type gemmaModel struct { + ModelParameters MaxPositionEmbeddings uint32 `json:"max_position_embeddings"` HiddenSize uint32 `json:"hidden_size"` HiddenLayers uint32 `json:"num_hidden_layers"` @@ -21,10 +21,10 @@ type gemma struct { HeadDim uint32 `json:"head_dim"` } -var _ Converter = (*gemma)(nil) +var _ ModelConverter = (*gemmaModel)(nil) -func (p *gemma) KV(t *Tokenizer) llm.KV { - kv := p.Parameters.KV(t) +func (p *gemmaModel) KV(t *Tokenizer) llm.KV { + kv := p.ModelParameters.KV(t) kv["general.architecture"] = "gemma" kv["gemma.context_length"] = p.MaxPositionEmbeddings kv["gemma.embedding_length"] = p.HiddenSize @@ -42,8 +42,8 @@ func (p *gemma) KV(t *Tokenizer) llm.KV { return kv } -func (p *gemma) Tensors(ts []Tensor) []llm.Tensor { - out := make([]llm.Tensor, 0, len(ts)) +func (p *gemmaModel) Tensors(ts []Tensor) []llm.Tensor { + var out []llm.Tensor for _, t := range ts { if strings.HasSuffix(t.Name(), "_norm.weight") { t.SetRepacker(p.addOne) @@ -60,7 +60,7 @@ func (p *gemma) Tensors(ts []Tensor) []llm.Tensor { return out } -func (p *gemma) Replacements() []string { +func (p *gemmaModel) Replacements() []string { return []string{ "model.embed_tokens", "token_embd", "model.norm", "output_norm", @@ -77,7 +77,7 @@ func (p *gemma) Replacements() []string { } } -func (*gemma) addOne(_ string, data []float32, shape []uint64) ([]float32, error) { +func (*gemmaModel) addOne(_ string, data []float32, shape []uint64) ([]float32, error) { n := tensor.New(tensor.WithShape(int(shape[0])), tensor.WithBacking(data)) ones := tensor.Ones(tensor.Float32, int(shape[0])) diff --git a/convert/convert_gemma2.go b/convert/convert_gemma2.go index 084f9c529..c4ee2d099 100644 --- a/convert/convert_gemma2.go +++ b/convert/convert_gemma2.go @@ -4,15 +4,15 @@ import ( "github.com/ollama/ollama/llm" ) -type gemma2 struct { - gemma +type gemma2Model struct { + gemmaModel SlidingWindow uint32 `json:"sliding_window"` AttentionLogitSoftcap float32 `json:"attn_logit_softcapping"` FinalLogitSoftcap float32 `json:"final_logit_softcapping"` } -func (p *gemma2) KV(t *Tokenizer) llm.KV { - kv := p.Parameters.KV(t) +func (p *gemma2Model) KV(t *Tokenizer) llm.KV { + kv := p.ModelParameters.KV(t) kv["general.architecture"] = "gemma2" kv["gemma2.context_length"] = p.MaxPositionEmbeddings kv["gemma2.embedding_length"] = p.HiddenSize @@ -33,9 +33,9 @@ func (p *gemma2) KV(t *Tokenizer) llm.KV { return kv } -func (p *gemma2) Replacements() []string { +func (p *gemma2Model) Replacements() []string { return append( - p.gemma.Replacements(), + p.gemmaModel.Replacements(), "post_attention_layernorm", "post_attention_norm", "pre_feedforward_layernorm", "ffn_norm", "post_feedforward_layernorm", "post_ffw_norm", diff --git a/convert/convert_gemma2_adapter.go b/convert/convert_gemma2_adapter.go new file mode 100644 index 000000000..a89a25f4c --- /dev/null +++ b/convert/convert_gemma2_adapter.go @@ -0,0 +1,91 @@ +package convert + +import ( + "strings" + + "github.com/pdevine/tensor" + "github.com/pdevine/tensor/native" + + "github.com/ollama/ollama/llm" +) + +type gemma2Adapter struct { + AdapterParameters +} + +var _ AdapterConverter = (*gemma2Adapter)(nil) + +func (p *gemma2Adapter) KV(baseKV llm.KV) llm.KV { + kv := p.AdapterParameters.KV() + kv["general.architecture"] = "gemma2" + return kv +} + +func (p *gemma2Adapter) Tensors(ts []Tensor) []llm.Tensor { + var out []llm.Tensor + for _, t := range ts { + shape := t.Shape() + if (strings.HasSuffix(t.Name(), "weight.lora_a") && shape[0] > shape[1]) || + (strings.HasSuffix(t.Name(), "weight.lora_b") && shape[0] < shape[1]) { + shape[0], shape[1] = shape[1], shape[0] + t.SetRepacker(p.repack) + } + + out = append(out, llm.Tensor{ + Name: t.Name(), + Kind: t.Kind(), + Shape: t.Shape(), + WriterTo: t, + }) + } + + return out +} + +func (p *gemma2Adapter) Replacements() []string { + return []string{ + "base_model.model.", "", + "model.layers", "blk", + "self_attn.q_proj", "attn_q", + "self_attn.k_proj", "attn_k", + "self_attn.v_proj", "attn_v", + "self_attn.o_proj", "attn_output", + "mlp.gate_proj", "ffn_gate", + "mlp.down_proj", "ffn_down", + "mlp.up_proj", "ffn_up", + "lora_A.weight", "weight.lora_a", + "lora_B.weight", "weight.lora_b", + "lora_a", "weight.lora_a", + "lora_b", "weight.lora_b", + } +} + +func (p *gemma2Adapter) repack(name string, data []float32, shape []uint64) ([]float32, error) { + dims := []int{int(shape[1]), int(shape[0])} + + n := tensor.New(tensor.WithShape(dims...), tensor.WithBacking(data)) + + if err := n.T(1, 0); err != nil { + return nil, err + } + + if err := n.Reshape(dims...); err != nil { + return nil, err + } + + if err := n.Transpose(); err != nil { + return nil, err + } + + ts, err := native.SelectF32(n, 1) + if err != nil { + return nil, err + } + + var f32s []float32 + for _, t := range ts { + f32s = append(f32s, t...) + } + + return f32s, nil +} diff --git a/convert/convert_llama.go b/convert/convert_llama.go index 27f924fbf..5dedb829d 100644 --- a/convert/convert_llama.go +++ b/convert/convert_llama.go @@ -12,8 +12,8 @@ import ( "github.com/ollama/ollama/llm" ) -type llama struct { - Parameters +type llamaModel struct { + ModelParameters NLayers uint32 `json:"n_layers"` NumHiddenLayers uint32 `json:"num_hidden_layers"` NLayer uint32 `json:"n_layer"` @@ -44,10 +44,10 @@ type llama struct { HeadDim uint32 `json:"head_dim"` } -var _ Converter = (*llama)(nil) +var _ ModelConverter = (*llamaModel)(nil) -func (p *llama) KV(t *Tokenizer) llm.KV { - kv := p.Parameters.KV(t) +func (p *llamaModel) KV(t *Tokenizer) llm.KV { + kv := p.ModelParameters.KV(t) kv["general.architecture"] = "llama" kv["llama.vocab_size"] = p.VocabSize @@ -120,7 +120,7 @@ func (p *llama) KV(t *Tokenizer) llm.KV { return kv } -func (p *llama) Tensors(ts []Tensor) []llm.Tensor { +func (p *llamaModel) Tensors(ts []Tensor) []llm.Tensor { var out []llm.Tensor if p.RopeScaling.factors != nil { @@ -149,7 +149,7 @@ func (p *llama) Tensors(ts []Tensor) []llm.Tensor { return out } -func (p *llama) Replacements() []string { +func (p *llamaModel) Replacements() []string { return []string{ "lm_head", "output", "model.embed_tokens", "token_embd", @@ -167,7 +167,7 @@ func (p *llama) Replacements() []string { } } -func (p *llama) repack(name string, data []float32, shape []uint64) ([]float32, error) { +func (p *llamaModel) repack(name string, data []float32, shape []uint64) ([]float32, error) { var dims []int for _, dim := range shape { dims = append(dims, int(dim)) diff --git a/convert/convert_llama_adapter.go b/convert/convert_llama_adapter.go new file mode 100644 index 000000000..08ddee10a --- /dev/null +++ b/convert/convert_llama_adapter.go @@ -0,0 +1,169 @@ +package convert + +import ( + "cmp" + "strings" + + "github.com/pdevine/tensor" + "github.com/pdevine/tensor/native" + + "github.com/ollama/ollama/llm" +) + +type llamaAdapter struct { + AdapterParameters + NumAttentionHeads uint32 `json:"num_attention_heads"` + NumKeyValueHeads uint32 `json:"num_key_value_heads"` +} + +var _ AdapterConverter = (*llamaAdapter)(nil) + +func (p *llamaAdapter) KV(baseKV llm.KV) llm.KV { + kv := p.AdapterParameters.KV() + kv["general.architecture"] = "llama" + kv["llama.attention.head_count"] = baseKV["llama.attention.head_count"] + kv["llama.attention.head_count_kv"] = baseKV["llama.attention.head_count_kv"] + + p.NumAttentionHeads = baseKV["llama.attention.head_count"].(uint32) + + return kv +} + +func (p *llamaAdapter) Tensors(ts []Tensor) []llm.Tensor { + var out []llm.Tensor + for _, t := range ts { + shape := t.Shape() + if (strings.HasSuffix(t.Name(), "weight.lora_a") && shape[0] > shape[1]) || + (strings.HasSuffix(t.Name(), "weight.lora_b") && shape[0] < shape[1]) { + shape[0], shape[1] = shape[1], shape[0] + t.SetRepacker(p.repackAndTranspose) + } else { + t.SetRepacker(p.repack) + } + + out = append(out, llm.Tensor{ + Name: t.Name(), + Kind: t.Kind(), + Shape: shape, + WriterTo: t, + }) + } + + return out +} + +func (p *llamaAdapter) Replacements() []string { + return []string{ + "base_model.model.", "", + "model.layers", "blk", + "self_attn.q_proj", "attn_q", + "self_attn.k_proj", "attn_k", + "self_attn.v_proj", "attn_v", + "self_attn.o_proj", "attn_output", + "mlp.gate_proj", "ffn_gate", + "mlp.down_proj", "ffn_down", + "mlp.up_proj", "ffn_up", + "lora_A.weight", "weight.lora_a", + "lora_B.weight", "weight.lora_b", + "lora_a", "weight.lora_a", + "lora_b", "weight.lora_b", + } +} + +func (p *llamaAdapter) repack(name string, data []float32, shape []uint64) ([]float32, error) { + dims := []int{int(shape[1]), int(shape[0])} + + var heads uint32 + if strings.HasSuffix(name, "attn_q.weight.lora_a") { + heads = p.NumAttentionHeads + } else if strings.HasSuffix(name, "attn_k.weight.lora_a") { + heads = cmp.Or(p.NumKeyValueHeads, p.NumAttentionHeads) + } else { + return data, nil + } + + n := tensor.New(tensor.WithShape(dims...), tensor.WithBacking(data)) + + if err := n.Reshape(append([]int{int(heads), 2, dims[0] / int(heads) / 2}, dims[1:]...)...); err != nil { + return nil, err + } + + if err := n.T(0, 2, 1, 3); err != nil { + return nil, err + } + + if err := n.Reshape(dims...); err != nil { + return nil, err + } + + if err := n.Transpose(); err != nil { + return nil, err + } + + ts, err := native.SelectF32(n, 1) + if err != nil { + return nil, err + } + + var f32s []float32 + for _, t := range ts { + f32s = append(f32s, t...) + } + + return f32s, nil +} + +func (p *llamaAdapter) repackAndTranspose(name string, data []float32, shape []uint64) ([]float32, error) { + dims := []int{int(shape[1]), int(shape[0])} + + n := tensor.New(tensor.WithShape(dims...), tensor.WithBacking(data)) + + var heads uint32 + if strings.HasSuffix(name, "attn_q.weight.lora_a") { + heads = p.NumAttentionHeads + } else if strings.HasSuffix(name, "attn_k.weight.lora_a") { + heads = cmp.Or(p.NumKeyValueHeads, p.NumAttentionHeads) + } + + if heads > 0 { + if err := n.Reshape(append([]int{int(heads), 2, dims[0] / int(heads) / 2}, dims[1:]...)...); err != nil { + return nil, err + } + + if err := n.T(0, 2, 1, 3); err != nil { + return nil, err + } + + if err := n.Reshape(dims...); err != nil { + return nil, err + } + + if err := n.Transpose(); err != nil { + return nil, err + } + } + + if err := n.T(1, 0); err != nil { + return nil, err + } + + if err := n.Reshape(dims...); err != nil { + return nil, err + } + + if err := n.Transpose(); err != nil { + return nil, err + } + + ts, err := native.SelectF32(n, 1) + if err != nil { + return nil, err + } + + var f32s []float32 + for _, t := range ts { + f32s = append(f32s, t...) + } + + return f32s, nil +} diff --git a/convert/convert_mixtral.go b/convert/convert_mixtral.go index 97a86b303..43b7c8b10 100644 --- a/convert/convert_mixtral.go +++ b/convert/convert_mixtral.go @@ -9,14 +9,14 @@ import ( "github.com/ollama/ollama/llm" ) -type mixtral struct { - llama +type mixtralModel struct { + llamaModel NumLocalExperts uint32 `json:"num_local_experts"` NumExpertsPerToken uint32 `json:"num_experts_per_tok"` } -func (p *mixtral) KV(t *Tokenizer) llm.KV { - kv := p.llama.KV(t) +func (p *mixtralModel) KV(t *Tokenizer) llm.KV { + kv := p.llamaModel.KV(t) if p.NumLocalExperts > 0 { kv["llama.expert_count"] = p.NumLocalExperts @@ -29,7 +29,7 @@ func (p *mixtral) KV(t *Tokenizer) llm.KV { return kv } -func (p *mixtral) Tensors(ts []Tensor) []llm.Tensor { +func (p *mixtralModel) Tensors(ts []Tensor) []llm.Tensor { oldnew := []string{ "model.layers", "blk", "w1", "ffn_gate_exps", @@ -67,12 +67,12 @@ func (p *mixtral) Tensors(ts []Tensor) []llm.Tensor { }) } - return append(out, p.llama.Tensors(ts)...) + return append(out, p.llamaModel.Tensors(ts)...) } -func (p *mixtral) Replacements() []string { +func (p *mixtralModel) Replacements() []string { return append( - p.llama.Replacements(), + p.llamaModel.Replacements(), "block_sparse_moe.gate", "ffn_gate_inp", ) } diff --git a/convert/convert_phi3.go b/convert/convert_phi3.go index 64d3d012e..3de0d4049 100644 --- a/convert/convert_phi3.go +++ b/convert/convert_phi3.go @@ -11,8 +11,8 @@ import ( "github.com/ollama/ollama/llm" ) -type phi3 struct { - Parameters +type phi3Model struct { + ModelParameters NumHiddenLayers uint32 `json:"num_hidden_layers"` NLayers uint32 `json:"n_layers"` HiddenSize uint32 `json:"hidden_size"` @@ -35,10 +35,10 @@ type phi3 struct { SlidingWindow uint32 `json:"sliding_window"` } -var _ Converter = (*phi3)(nil) +var _ ModelConverter = (*phi3Model)(nil) -func (p *phi3) KV(t *Tokenizer) llm.KV { - kv := p.Parameters.KV(t) +func (p *phi3Model) KV(t *Tokenizer) llm.KV { + kv := p.ModelParameters.KV(t) kv["general.architecture"] = "phi3" kv["phi3.context_length"] = p.MaxPositionEmbeddings kv["phi3.embedding_length"] = cmp.Or(p.HiddenSize, p.NEmbd) @@ -68,7 +68,7 @@ func (p *phi3) KV(t *Tokenizer) llm.KV { return kv } -func (p *phi3) Tensors(ts []Tensor) []llm.Tensor { +func (p *phi3Model) Tensors(ts []Tensor) []llm.Tensor { var addRopeFactors sync.Once out := make([]llm.Tensor, 0, len(ts)+2) @@ -100,7 +100,7 @@ func (p *phi3) Tensors(ts []Tensor) []llm.Tensor { return out } -func (p *phi3) Replacements() []string { +func (p *phi3Model) Replacements() []string { return []string{ "lm_head", "output", "model.embed_tokens", "token_embd", diff --git a/convert/convert_test.go b/convert/convert_test.go index 64b7df3b3..56b34f225 100644 --- a/convert/convert_test.go +++ b/convert/convert_test.go @@ -1,7 +1,9 @@ package convert import ( + "bytes" "crypto/sha256" + "encoding/binary" "encoding/hex" "encoding/json" "flag" @@ -29,7 +31,7 @@ func convertFull(t *testing.T, fsys fs.FS) (*os.File, llm.KV, llm.Tensors) { } defer f.Close() - if err := Convert(fsys, f); err != nil { + if err := ConvertModel(fsys, f); err != nil { t.Fatal(err) } @@ -51,6 +53,34 @@ func convertFull(t *testing.T, fsys fs.FS) (*os.File, llm.KV, llm.Tensors) { return r, m.KV(), m.Tensors() } +func generateResultsJSON(t *testing.T, f *os.File, kv llm.KV, tensors llm.Tensors) map[string]string { + actual := make(map[string]string) + for k, v := range kv { + if s, ok := v.(json.Marshaler); !ok { + actual[k] = fmt.Sprintf("%v", v) + } else { + bts, err := json.Marshal(s) + if err != nil { + t.Fatal(err) + } + + actual[k] = fmt.Sprintf("%x", sha256.Sum256(bts)) + } + } + + for _, tensor := range tensors.Items { + sha256sum := sha256.New() + sr := io.NewSectionReader(f, int64(tensors.Offset+tensor.Offset), int64(tensor.Size())) + if _, err := io.Copy(sha256sum, sr); err != nil { + t.Fatal(err) + } + + actual[tensor.Name] = hex.EncodeToString(sha256sum.Sum(nil)) + } + + return actual +} + func TestMain(m *testing.M) { var level slog.Level flag.TextVar(&level, "level", slog.LevelInfo, "log level") @@ -85,29 +115,7 @@ func TestConvertFull(t *testing.T) { } f, kv, tensors := convertFull(t, os.DirFS(p)) - actual := make(map[string]string) - for k, v := range kv { - if s, ok := v.(json.Marshaler); !ok { - actual[k] = fmt.Sprintf("%v", v) - } else { - bts, err := json.Marshal(s) - if err != nil { - t.Fatal(err) - } - - actual[k] = fmt.Sprintf("%x", sha256.Sum256(bts)) - } - } - - for _, tensor := range tensors.Items { - sha256sum := sha256.New() - sr := io.NewSectionReader(f, int64(tensors.Offset+tensor.Offset), int64(tensor.Size())) - if _, err := io.Copy(sha256sum, sr); err != nil { - t.Fatal(err) - } - - actual[tensor.Name] = hex.EncodeToString(sha256sum.Sum(nil)) - } + actual := generateResultsJSON(t, f, kv, tensors) expectFile, err := os.Open(filepath.Join("testdata", fmt.Sprintf("%s.json", tt))) if err != nil { @@ -131,3 +139,209 @@ func TestConvertFull(t *testing.T) { }) } } + +func TestConvertAdapter(t *testing.T) { + type AdapterCase struct { + Name string + BaseKV map[string]any + Expected map[string]string + } + + cases := []AdapterCase{ + { + Name: "discollama", + BaseKV: map[string]any{ + "general.architecture": "llama", + "llama.attention.head_count": uint32(32), + "llama.attention.head_count_kv": uint32(8), + }, + Expected: map[string]string{ + "general.architecture": "llama", + "general.file_type": "1", + "general.parameter_count": "106496", + "general.type": "adapter", + "general.version": "v0.2", + "adapter.lora.alpha": "16", + "adapter.type": "lora", + "llama.attention.head_count": "32", + "llama.attention.head_count_kv": "8", + "blk.31.attn_q.weight.lora_a": "0eb3318b02cd313429bcc7621b539fdbb10240fea190c56c9e5f93fcd37a4e50", + "blk.31.attn_q.weight.lora_b": "0eb3318b02cd313429bcc7621b539fdbb10240fea190c56c9e5f93fcd37a4e50", + "blk.31.attn_v.weight.lora_a": "0eb3318b02cd313429bcc7621b539fdbb10240fea190c56c9e5f93fcd37a4e50", + "blk.31.attn_v.weight.lora_b": "071dcafe89df065d6e1c935ecb8fdf6479b3c202eb912e7da938597673ff5857", + }, + }, + } + + for _, c := range cases { + t.Run(c.Name, func(t *testing.T) { + t.Parallel() + + f, err := os.CreateTemp(t.TempDir(), "f16") + if err != nil { + t.Fatal(err) + } + defer f.Close() + + tempDir := t.TempDir() + generateLoraTestData(t, tempDir) + + if err = ConvertAdapter(os.DirFS(tempDir), f, c.BaseKV); err != nil { + t.Fatal(err) + } + + r, err := os.Open(f.Name()) + if err != nil { + t.Fatal(err) + } + defer r.Close() + + m, _, err := llm.DecodeGGML(r, math.MaxInt) + if err != nil { + t.Fatal(err) + } + + if _, err := r.Seek(0, io.SeekStart); err != nil { + t.Fatal(err) + } + + actual := generateResultsJSON(t, r, m.KV(), m.Tensors()) + + keys := maps.Keys(c.Expected) + slices.Sort(keys) + for _, k := range keys { + if v, ok := actual[k]; !ok { + t.Errorf("missing %s", k) + } else if v != c.Expected[k] { + t.Errorf("unexpected %s: want %s, got %s", k, c.Expected[k], v) + } + } + }) + } +} + +func generateLoraTestData(t *testing.T, tempDir string) { + type tensorData struct { + Offsets []int `json:"data_offsets"` + Type string `json:"dtype"` + Shape []int `json:"shape"` + } + offset := 4096 * 8 * 4 + + td := map[string]*tensorData{"__metadata__": nil} + td["model.layers.31.self_attn.q_proj.lora_a"] = &tensorData{ + Offsets: []int{0, offset}, + Type: "F32", + Shape: []int{4096, 8}, + } + td["model.layers.31.self_attn.q_proj.lora_b"] = &tensorData{ + Offsets: []int{offset, offset * 2}, + Type: "F32", + Shape: []int{8, 4096}, + } + td["model.layers.31.self_attn.v_proj.lora_a"] = &tensorData{ + Offsets: []int{offset * 2, offset * 3}, + Type: "F32", + Shape: []int{4096, 8}, + } + td["model.layers.31.self_attn.v_proj.lora_b"] = &tensorData{ + Offsets: []int{offset * 3, offset*3 + 8*1024*4}, + Type: "F32", + Shape: []int{8, 1024}, + } + + data, err := json.Marshal(td) + if err != nil { + t.Fatal(err) + } + + var buf bytes.Buffer + + l := int64(len(data)) + err = binary.Write(&buf, binary.LittleEndian, l) + if err != nil { + t.Fatal(err) + } + + _, err = buf.Write(data) + if err != nil { + t.Fatal(err) + } + + // write some data for the tensors + + ones := make([]float32, 4096*8) + for i := range ones { + ones[i] = float32(1) + } + + for range 3 { + err = binary.Write(&buf, binary.LittleEndian, ones) + if err != nil { + t.Fatal(err) + } + } + + ones = make([]float32, 1024*8) + for i := range ones { + ones[i] = float32(1) + } + + err = binary.Write(&buf, binary.LittleEndian, ones) + if err != nil { + t.Fatal(err) + } + + fdata, err := os.Create(filepath.Join(tempDir, "adapters.safetensors")) + if err != nil { + t.Fatal(err) + } + defer fdata.Close() + + _, err = fdata.Write(buf.Bytes()) + if err != nil { + t.Fatal(err) + } + + configData := ` +{ + "adapter_path": "adapters-test", + "batch_size": 8, + "config": "config-tiny.json", + "data": "../discollama-completion", + "grad_checkpoint": null, + "iters": 1000, + "learning_rate": 1e-05, + "lora_layers": 1, + "lora_parameters": { + "rank": 8, + "alpha": 16, + "dropout": 0.0, + "scale": 2.0 + }, + "lr_schedule": null, + "max_seq_length": 2048, + "model": "/Users/pdevine/git/Meta-Llama-3-8B-Instruct", + "resume_adapter_file": null, + "save_every": 100, + "seed": 0, + "steps_per_eval": 200, + "steps_per_report": 10, + "test": false, + "test_batches": 500, + "train": true, + "use_dora": false, + "val_batches": 25 +} +` + f, err := os.Create(filepath.Join(tempDir, "adapter_config.json")) + if err != nil { + t.Fatal(err) + } + defer f.Close() + + _, err = f.WriteString(configData) + if err != nil { + t.Fatal(err) + } +} diff --git a/convert/reader.go b/convert/reader.go index 5bba0406b..c1218e66d 100644 --- a/convert/reader.go +++ b/convert/reader.go @@ -64,6 +64,8 @@ func parseTensors(fsys fs.FS, replacer *strings.Replacer) ([]Tensor, error) { }{ {"model-*-of-*.safetensors", parseSafetensors}, {"model.safetensors", parseSafetensors}, + {"adapters.safetensors", parseSafetensors}, + {"adapter_model.safetensors", parseSafetensors}, {"pytorch_model-*-of-*.bin", parseTorch}, {"pytorch_model.bin", parseTorch}, {"consolidated.*.pth", parseTorch}, diff --git a/llm/ggml.go b/llm/ggml.go index 4c68adf97..ab436095f 100644 --- a/llm/ggml.go +++ b/llm/ggml.go @@ -43,6 +43,14 @@ func (kv KV) Architecture() string { return "unknown" } +func (kv KV) Kind() string { + if s, ok := kv["general.type"].(string); ok { + return s + } + + return "unknown" +} + func (kv KV) ParameterCount() uint64 { return kv.u64("general.parameter_count") } diff --git a/server/images.go b/server/images.go index 8b3a67cf2..b5bf7ad64 100644 --- a/server/images.go +++ b/server/images.go @@ -369,13 +369,14 @@ func CreateModel(ctx context.Context, name model.Name, modelFileDir, quantizatio parameters := make(map[string]any) var layers []Layer + var baseLayers []*layerGGML for _, c := range modelfile.Commands { mediatype := fmt.Sprintf("application/vnd.ollama.image.%s", c.Name) + command := c.Name - switch c.Name { + switch command { case "model", "adapter": - var baseLayers []*layerGGML - if name := model.ParseName(c.Args); name.IsValid() { + if name := model.ParseName(c.Args); name.IsValid() && command == "model" { baseLayers, err = parseFromModel(ctx, name, fn) if err != nil { return err @@ -409,14 +410,14 @@ func CreateModel(ctx context.Context, name model.Name, modelFileDir, quantizatio } defer blob.Close() - baseLayers, err = parseFromFile(ctx, blob, digest, fn) + baseLayers, err = parseFromFile(ctx, command, baseLayers, blob, digest, fn) if err != nil { return err } } else if file, err := os.Open(realpath(modelFileDir, c.Args)); err == nil { defer file.Close() - baseLayers, err = parseFromFile(ctx, file, "", fn) + baseLayers, err = parseFromFile(ctx, command, baseLayers, file, "", fn) if err != nil { return err } diff --git a/server/model.go b/server/model.go index b17bf0e34..55fb2d8d3 100644 --- a/server/model.go +++ b/server/model.go @@ -81,7 +81,7 @@ func parseFromModel(ctx context.Context, name model.Name, fn func(api.ProgressRe return layers, nil } -func parseFromZipFile(_ context.Context, f *os.File, digest string, fn func(api.ProgressResponse)) (layers []*layerGGML, err error) { +func parseFromZipFile(_ context.Context, command string, baseLayers []*layerGGML, f *os.File, digest string, fn func(api.ProgressResponse)) (layers []*layerGGML, err error) { fi, err := f.Stat() if err != nil { return nil, err @@ -108,16 +108,38 @@ func parseFromZipFile(_ context.Context, f *os.File, digest string, fn func(api. defer t.Close() defer os.Remove(t.Name()) - fn(api.ProgressResponse{Status: "converting model"}) - if err := convert.Convert(convert.NewZipReader(r, p, 32<<20), t); err != nil { - return nil, err + var layerType string + + switch command { + case "adapter": + var baseModel *llm.GGML + for _, l := range baseLayers { + if l.GGML != nil { + baseModel = l.GGML + break + } + } + + if baseModel == nil { + return nil, fmt.Errorf("no base model specified for the adapter") + } + + if err := convert.ConvertAdapter(convert.NewZipReader(r, p, 32<<20), t, baseModel.KV()); err != nil { + return nil, err + } + layerType = "application/vnd.ollama.image.adapter" + case "model": + if err := convert.ConvertModel(convert.NewZipReader(r, p, 32<<20), t); err != nil { + return nil, err + } + layerType = "application/vnd.ollama.image.model" } if _, err := t.Seek(0, io.SeekStart); err != nil { return nil, err } - layer, err := NewLayer(t, "application/vnd.ollama.image.model") + layer, err := NewLayer(t, layerType) if err != nil { return nil, err } @@ -139,7 +161,7 @@ func parseFromZipFile(_ context.Context, f *os.File, digest string, fn func(api. return detectChatTemplate(layers) } -func parseFromFile(ctx context.Context, file *os.File, digest string, fn func(api.ProgressResponse)) (layers []*layerGGML, err error) { +func parseFromFile(ctx context.Context, command string, baseLayers []*layerGGML, file *os.File, digest string, fn func(api.ProgressResponse)) (layers []*layerGGML, err error) { sr := io.NewSectionReader(file, 0, 512) contentType, err := detectContentType(sr) if err != nil { @@ -150,7 +172,7 @@ func parseFromFile(ctx context.Context, file *os.File, digest string, fn func(ap case "gguf", "ggla": // noop case "application/zip": - return parseFromZipFile(ctx, file, digest, fn) + return parseFromZipFile(ctx, command, baseLayers, file, digest, fn) default: return nil, fmt.Errorf("unsupported content type: %s", contentType) } @@ -170,7 +192,7 @@ func parseFromFile(ctx context.Context, file *os.File, digest string, fn func(ap } mediatype := "application/vnd.ollama.image.model" - if ggml.Name() == "ggla" { + if ggml.Name() == "ggla" || ggml.KV().Kind() == "adapter" { mediatype = "application/vnd.ollama.image.adapter" } else if ggml.KV().Architecture() == "clip" { mediatype = "application/vnd.ollama.image.projector" diff --git a/server/model_test.go b/server/model_test.go index 63fc408d3..7753c5498 100644 --- a/server/model_test.go +++ b/server/model_test.go @@ -153,7 +153,7 @@ func TestParseFromFileFromLayer(t *testing.T) { t.Fatalf("failed to seek to start: %v", err) } - layers, err := parseFromFile(context.Background(), file, "", func(api.ProgressResponse) {}) + layers, err := parseFromFile(context.Background(), "model", []*layerGGML{}, file, "", func(api.ProgressResponse) {}) if err != nil { t.Fatalf("failed to parse from file: %v", err) } @@ -166,7 +166,7 @@ func TestParseFromFileFromLayer(t *testing.T) { t.Fatalf("failed to seek to start: %v", err) } - layers2, err := parseFromFile(context.Background(), file, layers[0].Digest, func(api.ProgressResponse) {}) + layers2, err := parseFromFile(context.Background(), "model", []*layerGGML{}, file, layers[0].Digest, func(api.ProgressResponse) {}) if err != nil { t.Fatalf("failed to parse from file: %v", err) } @@ -206,7 +206,7 @@ func TestParseLayerFromCopy(t *testing.T) { t.Fatalf("failed to seek to start: %v", err) } - layers, err := parseFromFile(context.Background(), file2, "", func(api.ProgressResponse) {}) + layers, err := parseFromFile(context.Background(), "model", []*layerGGML{}, file2, "", func(api.ProgressResponse) {}) if err != nil { t.Fatalf("failed to parse from file: %v", err) } From 386af6c1a0e9b1f4788b000ddf98955b4ce6183b Mon Sep 17 00:00:00 2001 From: Michael Yang Date: Fri, 23 Aug 2024 13:16:30 -0700 Subject: [PATCH 279/384] passthrough OLLAMA_HOST path to client --- envconfig/config.go | 10 +++------ envconfig/config_test.go | 47 ++++++++++++++++++++-------------------- 2 files changed, 27 insertions(+), 30 deletions(-) diff --git a/envconfig/config.go b/envconfig/config.go index 7e45a4f51..c13167b55 100644 --- a/envconfig/config.go +++ b/envconfig/config.go @@ -30,9 +30,7 @@ func Host() *url.URL { defaultPort = "443" } - // trim trailing slashes - hostport = strings.TrimRight(hostport, "/") - + hostport, path, _ := strings.Cut(hostport, "/") host, port, err := net.SplitHostPort(hostport) if err != nil { host, port = "127.0.0.1", defaultPort @@ -45,15 +43,13 @@ func Host() *url.URL { if n, err := strconv.ParseInt(port, 10, 32); err != nil || n > 65535 || n < 0 { slog.Warn("invalid port, using default", "port", port, "default", defaultPort) - return &url.URL{ - Scheme: scheme, - Host: net.JoinHostPort(host, defaultPort), - } + port = defaultPort } return &url.URL{ Scheme: scheme, Host: net.JoinHostPort(host, port), + Path: path, } } diff --git a/envconfig/config_test.go b/envconfig/config_test.go index 92a500f15..d52a98a5a 100644 --- a/envconfig/config_test.go +++ b/envconfig/config_test.go @@ -13,34 +13,35 @@ func TestHost(t *testing.T) { value string expect string }{ - "empty": {"", "127.0.0.1:11434"}, - "only address": {"1.2.3.4", "1.2.3.4:11434"}, - "only port": {":1234", ":1234"}, - "address and port": {"1.2.3.4:1234", "1.2.3.4:1234"}, - "hostname": {"example.com", "example.com:11434"}, - "hostname and port": {"example.com:1234", "example.com:1234"}, - "zero port": {":0", ":0"}, - "too large port": {":66000", ":11434"}, - "too small port": {":-1", ":11434"}, - "ipv6 localhost": {"[::1]", "[::1]:11434"}, - "ipv6 world open": {"[::]", "[::]:11434"}, - "ipv6 no brackets": {"::1", "[::1]:11434"}, - "ipv6 + port": {"[::1]:1337", "[::1]:1337"}, - "extra space": {" 1.2.3.4 ", "1.2.3.4:11434"}, - "extra quotes": {"\"1.2.3.4\"", "1.2.3.4:11434"}, - "extra space+quotes": {" \" 1.2.3.4 \" ", "1.2.3.4:11434"}, - "extra single quotes": {"'1.2.3.4'", "1.2.3.4:11434"}, - "http": {"http://1.2.3.4", "1.2.3.4:80"}, - "http port": {"http://1.2.3.4:4321", "1.2.3.4:4321"}, - "https": {"https://1.2.3.4", "1.2.3.4:443"}, - "https port": {"https://1.2.3.4:4321", "1.2.3.4:4321"}, + "empty": {"", "http://127.0.0.1:11434"}, + "only address": {"1.2.3.4", "http://1.2.3.4:11434"}, + "only port": {":1234", "http://:1234"}, + "address and port": {"1.2.3.4:1234", "http://1.2.3.4:1234"}, + "hostname": {"example.com", "http://example.com:11434"}, + "hostname and port": {"example.com:1234", "http://example.com:1234"}, + "zero port": {":0", "http://:0"}, + "too large port": {":66000", "http://:11434"}, + "too small port": {":-1", "http://:11434"}, + "ipv6 localhost": {"[::1]", "http://[::1]:11434"}, + "ipv6 world open": {"[::]", "http://[::]:11434"}, + "ipv6 no brackets": {"::1", "http://[::1]:11434"}, + "ipv6 + port": {"[::1]:1337", "http://[::1]:1337"}, + "extra space": {" 1.2.3.4 ", "http://1.2.3.4:11434"}, + "extra quotes": {"\"1.2.3.4\"", "http://1.2.3.4:11434"}, + "extra space+quotes": {" \" 1.2.3.4 \" ", "http://1.2.3.4:11434"}, + "extra single quotes": {"'1.2.3.4'", "http://1.2.3.4:11434"}, + "http": {"http://1.2.3.4", "http://1.2.3.4:80"}, + "http port": {"http://1.2.3.4:4321", "http://1.2.3.4:4321"}, + "https": {"https://1.2.3.4", "https://1.2.3.4:443"}, + "https port": {"https://1.2.3.4:4321", "https://1.2.3.4:4321"}, + "proxy path": {"https://example.com/ollama", "https://example.com:443/ollama"}, } for name, tt := range cases { t.Run(name, func(t *testing.T) { t.Setenv("OLLAMA_HOST", tt.value) - if host := Host(); host.Host != tt.expect { - t.Errorf("%s: expected %s, got %s", name, tt.expect, host.Host) + if host := Host(); host.String() != tt.expect { + t.Errorf("%s: expected %s, got %s", name, tt.expect, host.String()) } }) } From bb362caf88487a08cb29c8263d383a7d9e448803 Mon Sep 17 00:00:00 2001 From: Michael Yang Date: Tue, 2 Jul 2024 15:02:07 -0700 Subject: [PATCH 280/384] update faq --- docs/faq.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/docs/faq.md b/docs/faq.md index 324116d1a..25b682483 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -111,7 +111,10 @@ On Windows, Ollama inherits your user and system environment variables. ## How do I use Ollama behind a proxy? -Ollama is compatible with proxy servers if `HTTP_PROXY` or `HTTPS_PROXY` are configured. When using either variables, ensure it is set where `ollama serve` can access the values. When using `HTTPS_PROXY`, ensure the proxy certificate is installed as a system certificate. Refer to the section above for how to use environment variables on your platform. +Ollama pulls models from the Internet and may require a proxy server to access the models. Use `HTTPS_PROXY` to redirect outbound requests through the proxy. Ensure the proxy certificate is installed as a system certificate. Refer to the section above for how to use environment variables on your platform. + +> [!NOTE] +> Avoid setting `HTTP_PROXY`. Ollama does not use HTTP for model pulls, only HTTPS. Setting `HTTP_PROXY` may interrupt client connections to the server. ### How do I use Ollama behind a proxy in Docker? @@ -276,4 +279,4 @@ Note: Windows with Radeon GPUs currently default to 1 model maximum due to limit ## How does Ollama load models on multiple GPUs? -Installing multiple GPUs of the same brand can be a great way to increase your available VRAM to load larger models. When you load a new model, Ollama evaluates the required VRAM for the model against what is currently available. If the model will entirely fit on any single GPU, Ollama will load the model on that GPU. This typically provides the best performance as it reduces the amount of data transfering across the PCI bus during inference. If the model does not fit entirely on one GPU, then it will be spread across all the available GPUs. \ No newline at end of file +Installing multiple GPUs of the same brand can be a great way to increase your available VRAM to load larger models. When you load a new model, Ollama evaluates the required VRAM for the model against what is currently available. If the model will entirely fit on any single GPU, Ollama will load the model on that GPU. This typically provides the best performance as it reduces the amount of data transfering across the PCI bus during inference. If the model does not fit entirely on one GPU, then it will be spread across all the available GPUs. From 69be940bf6d2816f61c79facfa336183bc882720 Mon Sep 17 00:00:00 2001 From: Daniel Hiltgen Date: Fri, 23 Aug 2024 15:11:56 -0700 Subject: [PATCH 281/384] gpu: Group GPU Library sets by variant (#6483) The recent cuda variant changes uncovered a bug in ByLibrary which failed to group by common variant for GPU types. --- gpu/gpu_test.go | 25 +++++++++++++++++++++++++ gpu/types.go | 2 +- 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/gpu/gpu_test.go b/gpu/gpu_test.go index 46d3201e1..13a3f5442 100644 --- a/gpu/gpu_test.go +++ b/gpu/gpu_test.go @@ -32,4 +32,29 @@ func TestCPUMemInfo(t *testing.T) { } } +func TestByLibrary(t *testing.T) { + type testCase struct { + input []GpuInfo + expect int + } + + testCases := map[string]*testCase{ + "empty": {input: []GpuInfo{}, expect: 0}, + "cpu": {input: []GpuInfo{{Library: "cpu"}}, expect: 1}, + "cpu + GPU": {input: []GpuInfo{{Library: "cpu"}, {Library: "cuda"}}, expect: 2}, + "cpu + 2 GPU no variant": {input: []GpuInfo{{Library: "cpu"}, {Library: "cuda"}, {Library: "cuda"}}, expect: 2}, + "cpu + 2 GPU same variant": {input: []GpuInfo{{Library: "cpu"}, {Library: "cuda", Variant: "v11"}, {Library: "cuda", Variant: "v11"}}, expect: 2}, + "cpu + 2 GPU diff variant": {input: []GpuInfo{{Library: "cpu"}, {Library: "cuda", Variant: "v11"}, {Library: "cuda", Variant: "v12"}}, expect: 3}, + } + + for k, v := range testCases { + t.Run(k, func(t *testing.T) { + resp := (GpuInfoList)(v.input).ByLibrary() + if len(resp) != v.expect { + t.Fatalf("expected length %d, got %d => %+v", v.expect, len(resp), resp) + } + }) + } +} + // TODO - add some logic to figure out card type through other means and actually verify we got back what we expected diff --git a/gpu/types.go b/gpu/types.go index 4cbbeb841..a30e5fb36 100644 --- a/gpu/types.go +++ b/gpu/types.go @@ -94,7 +94,7 @@ func (l GpuInfoList) ByLibrary() []GpuInfoList { } } if !found { - libs = append(libs, info.Library) + libs = append(libs, requested) resp = append(resp, []GpuInfo{info}) } } From 0f92b19bec97198b035a7801eda14e3d48149033 Mon Sep 17 00:00:00 2001 From: Daniel Hiltgen Date: Sat, 24 Aug 2024 17:24:50 -0700 Subject: [PATCH 282/384] Only enable numa on CPUs (#6484) The numa flag may be having a performance impact on multi-socket systems with GPU loads --- llm/server.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/llm/server.go b/llm/server.go index 9347a4589..4e5dac283 100644 --- a/llm/server.go +++ b/llm/server.go @@ -258,7 +258,7 @@ func NewLlamaServer(gpus gpu.GpuInfoList, model string, ggml *GGML, adapters, pr params = append(params, "--mlock") } - if gpu.IsNUMA() { + if gpu.IsNUMA() && gpus[0].Library == "cpu" { numaMode := "distribute" if runtime.GOOS == "linux" { if _, err := exec.LookPath("numactl"); err == nil { From 47fa0839b970f7ad8dbb35344440bb51d5a7ba56 Mon Sep 17 00:00:00 2001 From: Jeffrey Morgan Date: Mon, 26 Aug 2024 19:36:11 -0700 Subject: [PATCH 283/384] server: clean up route names for consistency (#6524) --- server/routes.go | 36 ++++++++++++++++---------------- server/routes_create_test.go | 38 +++++++++++++++++----------------- server/routes_delete_test.go | 10 ++++----- server/routes_generate_test.go | 14 ++++++------- server/routes_list_test.go | 4 ++-- server/routes_test.go | 12 +++++------ 6 files changed, 57 insertions(+), 57 deletions(-) diff --git a/server/routes.go b/server/routes.go index 6c470c174..5e9f51e12 100644 --- a/server/routes.go +++ b/server/routes.go @@ -463,7 +463,7 @@ func (s *Server) EmbeddingsHandler(c *gin.Context) { c.JSON(http.StatusOK, resp) } -func (s *Server) PullModelHandler(c *gin.Context) { +func (s *Server) PullHandler(c *gin.Context) { var req api.PullRequest err := c.ShouldBindJSON(&req) switch { @@ -513,7 +513,7 @@ func (s *Server) PullModelHandler(c *gin.Context) { streamResponse(c, ch) } -func (s *Server) PushModelHandler(c *gin.Context) { +func (s *Server) PushHandler(c *gin.Context) { var req api.PushRequest err := c.ShouldBindJSON(&req) switch { @@ -577,7 +577,7 @@ func checkNameExists(name model.Name) error { return nil } -func (s *Server) CreateModelHandler(c *gin.Context) { +func (s *Server) CreateHandler(c *gin.Context) { var r api.CreateRequest if err := c.ShouldBindJSON(&r); errors.Is(err, io.EOF) { c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "missing request body"}) @@ -647,7 +647,7 @@ func (s *Server) CreateModelHandler(c *gin.Context) { streamResponse(c, ch) } -func (s *Server) DeleteModelHandler(c *gin.Context) { +func (s *Server) DeleteHandler(c *gin.Context) { var r api.DeleteRequest if err := c.ShouldBindJSON(&r); errors.Is(err, io.EOF) { c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "missing request body"}) @@ -680,7 +680,7 @@ func (s *Server) DeleteModelHandler(c *gin.Context) { } } -func (s *Server) ShowModelHandler(c *gin.Context) { +func (s *Server) ShowHandler(c *gin.Context) { var req api.ShowRequest err := c.ShouldBindJSON(&req) switch { @@ -829,7 +829,7 @@ func getKVData(digest string, verbose bool) (llm.KV, error) { return kv, nil } -func (s *Server) ListModelsHandler(c *gin.Context) { +func (s *Server) ListHandler(c *gin.Context) { ms, err := Manifests() if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) @@ -879,7 +879,7 @@ func (s *Server) ListModelsHandler(c *gin.Context) { c.JSON(http.StatusOK, api.ListResponse{Models: models}) } -func (s *Server) CopyModelHandler(c *gin.Context) { +func (s *Server) CopyHandler(c *gin.Context) { var r api.CopyRequest if err := c.ShouldBindJSON(&r); errors.Is(err, io.EOF) { c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "missing request body"}) @@ -1081,33 +1081,33 @@ func (s *Server) GenerateRoutes() http.Handler { allowedHostsMiddleware(s.addr), ) - r.POST("/api/pull", s.PullModelHandler) + r.POST("/api/pull", s.PullHandler) r.POST("/api/generate", s.GenerateHandler) r.POST("/api/chat", s.ChatHandler) r.POST("/api/embed", s.EmbedHandler) r.POST("/api/embeddings", s.EmbeddingsHandler) - r.POST("/api/create", s.CreateModelHandler) - r.POST("/api/push", s.PushModelHandler) - r.POST("/api/copy", s.CopyModelHandler) - r.DELETE("/api/delete", s.DeleteModelHandler) - r.POST("/api/show", s.ShowModelHandler) + r.POST("/api/create", s.CreateHandler) + r.POST("/api/push", s.PushHandler) + r.POST("/api/copy", s.CopyHandler) + r.DELETE("/api/delete", s.DeleteHandler) + r.POST("/api/show", s.ShowHandler) r.POST("/api/blobs/:digest", s.CreateBlobHandler) r.HEAD("/api/blobs/:digest", s.HeadBlobHandler) - r.GET("/api/ps", s.ProcessHandler) + r.GET("/api/ps", s.PsHandler) // Compatibility endpoints r.POST("/v1/chat/completions", openai.ChatMiddleware(), s.ChatHandler) r.POST("/v1/completions", openai.CompletionsMiddleware(), s.GenerateHandler) r.POST("/v1/embeddings", openai.EmbeddingsMiddleware(), s.EmbedHandler) - r.GET("/v1/models", openai.ListMiddleware(), s.ListModelsHandler) - r.GET("/v1/models/:model", openai.RetrieveMiddleware(), s.ShowModelHandler) + r.GET("/v1/models", openai.ListMiddleware(), s.ListHandler) + r.GET("/v1/models/:model", openai.RetrieveMiddleware(), s.ShowHandler) for _, method := range []string{http.MethodGet, http.MethodHead} { r.Handle(method, "/", func(c *gin.Context) { c.String(http.StatusOK, "Ollama is running") }) - r.Handle(method, "/api/tags", s.ListModelsHandler) + r.Handle(method, "/api/tags", s.ListHandler) r.Handle(method, "/api/version", func(c *gin.Context) { c.JSON(http.StatusOK, gin.H{"version": version.Version}) }) @@ -1269,7 +1269,7 @@ func streamResponse(c *gin.Context, ch chan any) { }) } -func (s *Server) ProcessHandler(c *gin.Context) { +func (s *Server) PsHandler(c *gin.Context) { models := []api.ProcessModelResponse{} for _, v := range s.sched.loaded { diff --git a/server/routes_create_test.go b/server/routes_create_test.go index 4de07b252..d436f26cd 100644 --- a/server/routes_create_test.go +++ b/server/routes_create_test.go @@ -93,7 +93,7 @@ func TestCreateFromBin(t *testing.T) { t.Setenv("OLLAMA_MODELS", p) var s Server - w := createRequest(t, s.CreateModelHandler, api.CreateRequest{ + w := createRequest(t, s.CreateHandler, api.CreateRequest{ Name: "test", Modelfile: fmt.Sprintf("FROM %s", createBinFile(t, nil, nil)), Stream: &stream, @@ -120,7 +120,7 @@ func TestCreateFromModel(t *testing.T) { t.Setenv("OLLAMA_MODELS", p) var s Server - w := createRequest(t, s.CreateModelHandler, api.CreateRequest{ + w := createRequest(t, s.CreateHandler, api.CreateRequest{ Name: "test", Modelfile: fmt.Sprintf("FROM %s", createBinFile(t, nil, nil)), Stream: &stream, @@ -134,7 +134,7 @@ func TestCreateFromModel(t *testing.T) { filepath.Join(p, "manifests", "registry.ollama.ai", "library", "test", "latest"), }) - w = createRequest(t, s.CreateModelHandler, api.CreateRequest{ + w = createRequest(t, s.CreateHandler, api.CreateRequest{ Name: "test2", Modelfile: "FROM test", Stream: &stream, @@ -162,7 +162,7 @@ func TestCreateRemovesLayers(t *testing.T) { t.Setenv("OLLAMA_MODELS", p) var s Server - w := createRequest(t, s.CreateModelHandler, api.CreateRequest{ + w := createRequest(t, s.CreateHandler, api.CreateRequest{ Name: "test", Modelfile: fmt.Sprintf("FROM %s\nTEMPLATE {{ .Prompt }}", createBinFile(t, nil, nil)), Stream: &stream, @@ -182,7 +182,7 @@ func TestCreateRemovesLayers(t *testing.T) { filepath.Join(p, "blobs", "sha256-bc80b03733773e0728011b2f4adf34c458b400e1aad48cb28d61170f3a2ad2d6"), }) - w = createRequest(t, s.CreateModelHandler, api.CreateRequest{ + w = createRequest(t, s.CreateHandler, api.CreateRequest{ Name: "test", Modelfile: fmt.Sprintf("FROM %s\nTEMPLATE {{ .System }} {{ .Prompt }}", createBinFile(t, nil, nil)), Stream: &stream, @@ -210,7 +210,7 @@ func TestCreateUnsetsSystem(t *testing.T) { t.Setenv("OLLAMA_MODELS", p) var s Server - w := createRequest(t, s.CreateModelHandler, api.CreateRequest{ + w := createRequest(t, s.CreateHandler, api.CreateRequest{ Name: "test", Modelfile: fmt.Sprintf("FROM %s\nSYSTEM Say hi!", createBinFile(t, nil, nil)), Stream: &stream, @@ -230,7 +230,7 @@ func TestCreateUnsetsSystem(t *testing.T) { filepath.Join(p, "blobs", "sha256-f29e82a8284dbdf5910b1555580ff60b04238b8da9d5e51159ada67a4d0d5851"), }) - w = createRequest(t, s.CreateModelHandler, api.CreateRequest{ + w = createRequest(t, s.CreateHandler, api.CreateRequest{ Name: "test", Modelfile: fmt.Sprintf("FROM %s\nSYSTEM \"\"", createBinFile(t, nil, nil)), Stream: &stream, @@ -267,7 +267,7 @@ func TestCreateMergeParameters(t *testing.T) { t.Setenv("OLLAMA_MODELS", p) var s Server - w := createRequest(t, s.CreateModelHandler, api.CreateRequest{ + w := createRequest(t, s.CreateHandler, api.CreateRequest{ Name: "test", Modelfile: fmt.Sprintf("FROM %s\nPARAMETER temperature 1\nPARAMETER top_k 10\nPARAMETER stop USER:\nPARAMETER stop ASSISTANT:", createBinFile(t, nil, nil)), Stream: &stream, @@ -288,7 +288,7 @@ func TestCreateMergeParameters(t *testing.T) { }) // in order to merge parameters, the second model must be created FROM the first - w = createRequest(t, s.CreateModelHandler, api.CreateRequest{ + w = createRequest(t, s.CreateHandler, api.CreateRequest{ Name: "test2", Modelfile: "FROM test\nPARAMETER temperature 0.6\nPARAMETER top_p 0.7", Stream: &stream, @@ -326,7 +326,7 @@ func TestCreateMergeParameters(t *testing.T) { } // slices are replaced - w = createRequest(t, s.CreateModelHandler, api.CreateRequest{ + w = createRequest(t, s.CreateHandler, api.CreateRequest{ Name: "test2", Modelfile: "FROM test\nPARAMETER temperature 0.6\nPARAMETER top_p 0.7\nPARAMETER stop <|endoftext|>", Stream: &stream, @@ -371,7 +371,7 @@ func TestCreateReplacesMessages(t *testing.T) { t.Setenv("OLLAMA_MODELS", p) var s Server - w := createRequest(t, s.CreateModelHandler, api.CreateRequest{ + w := createRequest(t, s.CreateHandler, api.CreateRequest{ Name: "test", Modelfile: fmt.Sprintf("FROM %s\nMESSAGE assistant \"What is my purpose?\"\nMESSAGE user \"You run tests.\"\nMESSAGE assistant \"Oh, my god.\"", createBinFile(t, nil, nil)), Stream: &stream, @@ -391,7 +391,7 @@ func TestCreateReplacesMessages(t *testing.T) { filepath.Join(p, "blobs", "sha256-e0e27d47045063ccb167ae852c51d49a98eab33fabaee4633fdddf97213e40b5"), }) - w = createRequest(t, s.CreateModelHandler, api.CreateRequest{ + w = createRequest(t, s.CreateHandler, api.CreateRequest{ Name: "test2", Modelfile: "FROM test\nMESSAGE assistant \"You're a test, Harry.\"\nMESSAGE user \"I-I'm a what?\"\nMESSAGE assistant \"A test. And a thumping good one at that, I'd wager.\"", Stream: &stream, @@ -448,7 +448,7 @@ func TestCreateTemplateSystem(t *testing.T) { t.Setenv("OLLAMA_MODELS", p) var s Server - w := createRequest(t, s.CreateModelHandler, api.CreateRequest{ + w := createRequest(t, s.CreateHandler, api.CreateRequest{ Name: "test", Modelfile: fmt.Sprintf("FROM %s\nTEMPLATE {{ .Prompt }}\nSYSTEM Say hello!\nTEMPLATE {{ .System }} {{ .Prompt }}\nSYSTEM Say bye!", createBinFile(t, nil, nil)), Stream: &stream, @@ -488,7 +488,7 @@ func TestCreateTemplateSystem(t *testing.T) { } t.Run("incomplete template", func(t *testing.T) { - w := createRequest(t, s.CreateModelHandler, api.CreateRequest{ + w := createRequest(t, s.CreateHandler, api.CreateRequest{ Name: "test", Modelfile: fmt.Sprintf("FROM %s\nTEMPLATE {{ .Prompt", createBinFile(t, nil, nil)), Stream: &stream, @@ -500,7 +500,7 @@ func TestCreateTemplateSystem(t *testing.T) { }) t.Run("template with unclosed if", func(t *testing.T) { - w := createRequest(t, s.CreateModelHandler, api.CreateRequest{ + w := createRequest(t, s.CreateHandler, api.CreateRequest{ Name: "test", Modelfile: fmt.Sprintf("FROM %s\nTEMPLATE {{ if .Prompt }}", createBinFile(t, nil, nil)), Stream: &stream, @@ -512,7 +512,7 @@ func TestCreateTemplateSystem(t *testing.T) { }) t.Run("template with undefined function", func(t *testing.T) { - w := createRequest(t, s.CreateModelHandler, api.CreateRequest{ + w := createRequest(t, s.CreateHandler, api.CreateRequest{ Name: "test", Modelfile: fmt.Sprintf("FROM %s\nTEMPLATE {{ Prompt }}", createBinFile(t, nil, nil)), Stream: &stream, @@ -531,7 +531,7 @@ func TestCreateLicenses(t *testing.T) { t.Setenv("OLLAMA_MODELS", p) var s Server - w := createRequest(t, s.CreateModelHandler, api.CreateRequest{ + w := createRequest(t, s.CreateHandler, api.CreateRequest{ Name: "test", Modelfile: fmt.Sprintf("FROM %s\nLICENSE MIT\nLICENSE Apache-2.0", createBinFile(t, nil, nil)), Stream: &stream, @@ -579,7 +579,7 @@ func TestCreateDetectTemplate(t *testing.T) { var s Server t.Run("matched", func(t *testing.T) { - w := createRequest(t, s.CreateModelHandler, api.CreateRequest{ + w := createRequest(t, s.CreateHandler, api.CreateRequest{ Name: "test", Modelfile: fmt.Sprintf("FROM %s", createBinFile(t, llm.KV{ "tokenizer.chat_template": "{{ bos_token }}{% for message in messages %}{{'<|' + message['role'] + '|>' + '\n' + message['content'] + '<|end|>\n' }}{% endfor %}{% if add_generation_prompt %}{{ '<|assistant|>\n' }}{% else %}{{ eos_token }}{% endif %}", @@ -600,7 +600,7 @@ func TestCreateDetectTemplate(t *testing.T) { }) t.Run("unmatched", func(t *testing.T) { - w := createRequest(t, s.CreateModelHandler, api.CreateRequest{ + w := createRequest(t, s.CreateHandler, api.CreateRequest{ Name: "test", Modelfile: fmt.Sprintf("FROM %s", createBinFile(t, nil, nil)), Stream: &stream, diff --git a/server/routes_delete_test.go b/server/routes_delete_test.go index 82fac9f52..5a337e794 100644 --- a/server/routes_delete_test.go +++ b/server/routes_delete_test.go @@ -22,7 +22,7 @@ func TestDelete(t *testing.T) { var s Server - w := createRequest(t, s.CreateModelHandler, api.CreateRequest{ + w := createRequest(t, s.CreateHandler, api.CreateRequest{ Name: "test", Modelfile: fmt.Sprintf("FROM %s", createBinFile(t, nil, nil)), }) @@ -31,7 +31,7 @@ func TestDelete(t *testing.T) { t.Fatalf("expected status code 200, actual %d", w.Code) } - w = createRequest(t, s.CreateModelHandler, api.CreateRequest{ + w = createRequest(t, s.CreateHandler, api.CreateRequest{ Name: "test2", Modelfile: fmt.Sprintf("FROM %s\nTEMPLATE {{ .System }} {{ .Prompt }}", createBinFile(t, nil, nil)), }) @@ -52,7 +52,7 @@ func TestDelete(t *testing.T) { filepath.Join(p, "blobs", "sha256-fe7ac77b725cda2ccad03f88a880ecdfd7a33192d6cae08fce2c0ee1455991ed"), }) - w = createRequest(t, s.DeleteModelHandler, api.DeleteRequest{Name: "test"}) + w = createRequest(t, s.DeleteHandler, api.DeleteRequest{Name: "test"}) if w.Code != http.StatusOK { t.Fatalf("expected status code 200, actual %d", w.Code) @@ -68,7 +68,7 @@ func TestDelete(t *testing.T) { filepath.Join(p, "blobs", "sha256-fe7ac77b725cda2ccad03f88a880ecdfd7a33192d6cae08fce2c0ee1455991ed"), }) - w = createRequest(t, s.DeleteModelHandler, api.DeleteRequest{Name: "test2"}) + w = createRequest(t, s.DeleteHandler, api.DeleteRequest{Name: "test2"}) if w.Code != http.StatusOK { t.Fatalf("expected status code 200, actual %d", w.Code) @@ -102,7 +102,7 @@ func TestDeleteDuplicateLayers(t *testing.T) { t.Fatal(err) } - w := createRequest(t, s.DeleteModelHandler, api.DeleteRequest{Name: "test"}) + w := createRequest(t, s.DeleteHandler, api.DeleteRequest{Name: "test"}) if w.Code != http.StatusOK { t.Errorf("expected status code 200, actual %d", w.Code) } diff --git a/server/routes_generate_test.go b/server/routes_generate_test.go index 5c0caff1c..480b96721 100644 --- a/server/routes_generate_test.go +++ b/server/routes_generate_test.go @@ -84,7 +84,7 @@ func TestGenerateChat(t *testing.T) { go s.sched.Run(context.TODO()) - w := createRequest(t, s.CreateModelHandler, api.CreateRequest{ + w := createRequest(t, s.CreateHandler, api.CreateRequest{ Model: "test", Modelfile: fmt.Sprintf(`FROM %s TEMPLATE """ @@ -144,7 +144,7 @@ func TestGenerateChat(t *testing.T) { }) t.Run("missing capabilities chat", func(t *testing.T) { - w := createRequest(t, s.CreateModelHandler, api.CreateRequest{ + w := createRequest(t, s.CreateHandler, api.CreateRequest{ Model: "bert", Modelfile: fmt.Sprintf("FROM %s", createBinFile(t, llm.KV{ "general.architecture": "bert", @@ -270,7 +270,7 @@ func TestGenerateChat(t *testing.T) { checkChatResponse(t, w.Body, "test", "Hi!") }) - w = createRequest(t, s.CreateModelHandler, api.CreateRequest{ + w = createRequest(t, s.CreateHandler, api.CreateRequest{ Model: "test-system", Modelfile: "FROM test\nSYSTEM You are a helpful assistant.", }) @@ -382,7 +382,7 @@ func TestGenerate(t *testing.T) { go s.sched.Run(context.TODO()) - w := createRequest(t, s.CreateModelHandler, api.CreateRequest{ + w := createRequest(t, s.CreateHandler, api.CreateRequest{ Model: "test", Modelfile: fmt.Sprintf(`FROM %s TEMPLATE """ @@ -442,7 +442,7 @@ func TestGenerate(t *testing.T) { }) t.Run("missing capabilities generate", func(t *testing.T) { - w := createRequest(t, s.CreateModelHandler, api.CreateRequest{ + w := createRequest(t, s.CreateHandler, api.CreateRequest{ Model: "bert", Modelfile: fmt.Sprintf("FROM %s", createBinFile(t, llm.KV{ "general.architecture": "bert", @@ -583,7 +583,7 @@ func TestGenerate(t *testing.T) { checkGenerateResponse(t, w.Body, "test", "Hi!") }) - w = createRequest(t, s.CreateModelHandler, api.CreateRequest{ + w = createRequest(t, s.CreateHandler, api.CreateRequest{ Model: "test-system", Modelfile: "FROM test\nSYSTEM You are a helpful assistant.", }) @@ -652,7 +652,7 @@ func TestGenerate(t *testing.T) { checkGenerateResponse(t, w.Body, "test-system", "Abra kadabra!") }) - w = createRequest(t, s.CreateModelHandler, api.CreateRequest{ + w = createRequest(t, s.CreateHandler, api.CreateRequest{ Model: "test-suffix", Modelfile: `FROM test TEMPLATE """{{- if .Suffix }}
 {{ .Prompt }} {{ .Suffix }} 
diff --git a/server/routes_list_test.go b/server/routes_list_test.go
index 6e92b7a1a..56b408300 100644
--- a/server/routes_list_test.go
+++ b/server/routes_list_test.go
@@ -31,13 +31,13 @@ func TestList(t *testing.T) {
 
 	var s Server
 	for _, n := range expectNames {
-		createRequest(t, s.CreateModelHandler, api.CreateRequest{
+		createRequest(t, s.CreateHandler, api.CreateRequest{
 			Name:      n,
 			Modelfile: fmt.Sprintf("FROM %s", createBinFile(t, nil, nil)),
 		})
 	}
 
-	w := createRequest(t, s.ListModelsHandler, nil)
+	w := createRequest(t, s.ListHandler, nil)
 	if w.Code != http.StatusOK {
 		t.Fatalf("expected status code 200, actual %d", w.Code)
 	}
diff --git a/server/routes_test.go b/server/routes_test.go
index 242875d6c..bffcea205 100644
--- a/server/routes_test.go
+++ b/server/routes_test.go
@@ -318,7 +318,7 @@ func TestCase(t *testing.T) {
 	var s Server
 	for _, tt := range cases {
 		t.Run(tt, func(t *testing.T) {
-			w := createRequest(t, s.CreateModelHandler, api.CreateRequest{
+			w := createRequest(t, s.CreateHandler, api.CreateRequest{
 				Name:      tt,
 				Modelfile: fmt.Sprintf("FROM %s", createBinFile(t, nil, nil)),
 				Stream:    &stream,
@@ -334,7 +334,7 @@ func TestCase(t *testing.T) {
 			}
 
 			t.Run("create", func(t *testing.T) {
-				w = createRequest(t, s.CreateModelHandler, api.CreateRequest{
+				w = createRequest(t, s.CreateHandler, api.CreateRequest{
 					Name:      strings.ToUpper(tt),
 					Modelfile: fmt.Sprintf("FROM %s", createBinFile(t, nil, nil)),
 					Stream:    &stream,
@@ -350,7 +350,7 @@ func TestCase(t *testing.T) {
 			})
 
 			t.Run("pull", func(t *testing.T) {
-				w := createRequest(t, s.PullModelHandler, api.PullRequest{
+				w := createRequest(t, s.PullHandler, api.PullRequest{
 					Name:   strings.ToUpper(tt),
 					Stream: &stream,
 				})
@@ -365,7 +365,7 @@ func TestCase(t *testing.T) {
 			})
 
 			t.Run("copy", func(t *testing.T) {
-				w := createRequest(t, s.CopyModelHandler, api.CopyRequest{
+				w := createRequest(t, s.CopyHandler, api.CopyRequest{
 					Source:      tt,
 					Destination: strings.ToUpper(tt),
 				})
@@ -387,7 +387,7 @@ func TestShow(t *testing.T) {
 
 	var s Server
 
-	createRequest(t, s.CreateModelHandler, api.CreateRequest{
+	createRequest(t, s.CreateHandler, api.CreateRequest{
 		Name: "show-model",
 		Modelfile: fmt.Sprintf(
 			"FROM %s\nFROM %s",
@@ -396,7 +396,7 @@ func TestShow(t *testing.T) {
 		),
 	})
 
-	w := createRequest(t, s.ShowModelHandler, api.ShowRequest{
+	w := createRequest(t, s.ShowHandler, api.ShowRequest{
 		Name: "show-model",
 	})
 

From ac80010db8ceebac6891beea9a5cd761815678ad Mon Sep 17 00:00:00 2001
From: Patrick Devine 
Date: Mon, 26 Aug 2024 19:57:26 -0700
Subject: [PATCH 284/384] update the import docs (#6104)

---
 docs/images/ollama-keys.png | Bin 0 -> 144101 bytes
 docs/images/signup.png      | Bin 0 -> 81360 bytes
 docs/import.md              | 188 +++++++++++++++++++++++++++---------
 3 files changed, 142 insertions(+), 46 deletions(-)
 create mode 100644 docs/images/ollama-keys.png
 create mode 100644 docs/images/signup.png

diff --git a/docs/images/ollama-keys.png b/docs/images/ollama-keys.png
new file mode 100644
index 0000000000000000000000000000000000000000..119a4bcf4cb0cf0639f3e71e3cb70010f8179316
GIT binary patch
literal 144101
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
E3ov}7S^xk5

literal 0
HcmV?d00001

diff --git a/docs/import.md b/docs/import.md
index 82ea9ba5d..baf345506 100644
--- a/docs/import.md
+++ b/docs/import.md
@@ -1,44 +1,129 @@
-# Import
+# Importing a model
 
-GGUF models and select Safetensors models can be imported directly into Ollama.
+## Table of Contents
 
-## Import GGUF
+  * [Importing a Safetensors adapter](#Importing-a-fine-tuned-adapter-from-Safetensors-weights)
+  * [Importing a Safetensors model](#Importing-a-model-from-Safetensors-weights)
+  * [Importing a GGUF file](#Importing-a-GGUF-based-model-or-adapter)
+  * [Sharing models on ollama.com](#Sharing-your-model-on-ollama.com)
 
-A binary GGUF file can be imported directly into Ollama through a Modelfile.
+## Importing a fine tuned adapter from Safetensors weights
+
+First, create a `Modelfile` with a `FROM` command pointing at the base model you used for fine tuning, and an `ADAPTER` command which points to the directory with your Safetensors adapter:
 
 ```dockerfile
-FROM /path/to/file.gguf
+FROM 
+ADAPTER /path/to/safetensors/adapter/directory
 ```
 
-## Import Safetensors
+Make sure that you use the same base model in the `FROM` command as you used to create the adapter otherwise you will get erratic results. Most frameworks use different quantization methods, so it's best to use non-quantized (i.e. non-QLoRA) adapters. If your adapter is in the same directory as your `Modelfile`, use `ADAPTER .` to specify the adapter path.
 
-If the model being imported is one of these architectures, it can be imported directly into Ollama through a Modelfile:
+Now run `ollama create` from the directory where the `Modelfile` was created:
 
- - LlamaForCausalLM
- - MistralForCausalLM
- - MixtralForCausalLM
- - GemmaForCausalLM
- - Phi3ForCausalLM
+```bash
+ollama create my-model
+```
+
+Lastly, test the model:
+
+```bash
+ollama run my-model
+```
+
+Ollama supports importing adapters based on several different model architectures including:
+
+  * Llama (including Llama 2, Llama 3, and Llama 3.1);
+  * Mistral (including Mistral 1, Mistral 2, and Mixtral); and
+  * Gemma (including Gemma 1 and Gemma 2)
+
+You can create the adapter using a fine tuning framework or tool which can output adapters in the Safetensors format, such as:
+
+  * Hugging Face [fine tuning framework] (https://huggingface.co/docs/transformers/en/training)
+  * [Unsloth](https://github.com/unslothai/unsloth)
+  * [MLX](https://github.com/ml-explore/mlx)
+
+
+## Importing a model from Safetensors weights
+
+First, create a `Modelfile` with a `FROM` command which points to the directory containing your Safetensors weights:
 
 ```dockerfile
 FROM /path/to/safetensors/directory
 ```
 
-For architectures not directly convertable by Ollama, see llama.cpp's [guide](https://github.com/ggerganov/llama.cpp/blob/master/README.md#prepare-and-quantize) on conversion. After conversion, see [Import GGUF](#import-gguf).
+If you create the Modelfile in the same directory as the weights, you can use the command `FROM .`.
 
-## Automatic Quantization
+Now run the `ollama create` command from the directory where you created the `Modelfile`:
 
-> [!NOTE]
-> Automatic quantization requires v0.1.35 or higher.
+```shell
+ollama create my-model
+```
 
-Ollama is capable of quantizing FP16 or FP32 models to any of the supported quantizations with the `-q/--quantize` flag in `ollama create`.
+Lastly, test the model:
+
+```shell
+ollama run my-model
+```
+
+Ollama supports importing models for several different architectures including:
+
+  * Llama (including Llama 2, Llama 3, and Llama 3.1);
+  * Mistral (including Mistral 1, Mistral 2, and Mixtral);
+  * Gemma (including Gemma 1 and Gemma 2); and
+  * Phi3
+
+This includes importing foundation models as well as any fine tuned models which which have been _fused_ with a foundation model.
+
+
+## Importing a GGUF based model or adapter
+
+If you have a GGUF based model or adapter it is possible to import it into Ollama. You can obtain a GGUF model or adapter by:
+
+  * converting a Safetensors model with the `convert_hf_to_gguf.py` from Llama.cpp; 
+  * converting a Safetensors adapter with the `convert_lora_to_gguf.py` from Llama.cpp; or
+  * downloading a model or adapter from a place such as HuggingFace
+
+To import a GGUF model, create a `Modelfile` containg:
+
+```dockerfile
+FROM /path/to/file.gguf
+```
+
+For a GGUF adapter, create the `Modelfile` with:
+
+```dockerfile
+FROM 
+ADAPTER /path/to/file.gguf
+```
+
+When importing a GGUF adapter, it's important to use the same base model as the base model that the adapter was created with. You can use:
+
+ * a model from Ollama
+ * a GGUF file
+ * a Safetensors based model 
+
+Once you have created your `Modelfile`, use the `ollama create` command to build the model.
+
+```shell
+ollama create my-model
+```
+
+## Quantizing a Model
+
+Quantizing a model allows you to run models faster and with less memory consumption but at reduced accuracy. This allows you to run a model on more modest hardware.
+
+Ollama can quantize FP16 and FP32 based models into different quantization levels using the `-q/--quantize` flag with the `ollama create` command.
+
+First, create a Modelfile with the FP16 or FP32 based model you wish to quantize.
 
 ```dockerfile
 FROM /path/to/my/gemma/f16/model
 ```
 
+Use `ollama create` to then create the quantized model.
+
 ```shell
-$ ollama create -q Q4_K_M mymodel
+$ ollama create --quantize q4_K_M mymodel
 transferring model data
 quantizing F16 model to Q4_K_M
 creating new layer sha256:735e246cc1abfd06e9cdcf95504d6789a6cd1ad7577108a70d9902fef503c1bd
@@ -49,42 +134,53 @@ success
 
 ### Supported Quantizations
 
-- `Q4_0`
-- `Q4_1`
-- `Q5_0`
-- `Q5_1`
-- `Q8_0`
+- `q4_0`
+- `q4_1`
+- `q5_0`
+- `q5_1`
+- `q8_0`
 
 #### K-means Quantizations
 
-- `Q3_K_S`
-- `Q3_K_M`
-- `Q3_K_L`
-- `Q4_K_S`
-- `Q4_K_M`
-- `Q5_K_S`
-- `Q5_K_M`
-- `Q6_K`
+- `q3_K_S`
+- `q3_K_M`
+- `q3_K_L`
+- `q4_K_S`
+- `q4_K_M`
+- `q5_K_S`
+- `q5_K_M`
+- `q6_K`
 
-## Template Detection
 
-> [!NOTE]
-> Template detection requires v0.1.42 or higher.
+## Sharing your model on ollama.com
 
-Ollama uses model metadata, specifically `tokenizer.chat_template`, to automatically create a template appropriate for the model you're importing.
+You can share any model you have created by pushing it to [ollama.com](https://ollama.com) so that other users can try it out.
 
-```dockerfile
-FROM /path/to/my/gemma/model
-```
+First, use your browser to go to the [Ollama Sign-Up](https://ollama.com/signup) page. If you already have an account, you can skip this step.
+
+![Sign-Up](images/signup.png)
+
+The `Username` field will be used as part of your model's name (e.g. `jmorganca/mymodel`), so make sure you are comfortable with the username that you have selected.
+
+Now that you have created an account and are signed-in, go to the [Ollama Keys Settings](https://ollama.com/settings/keys) page.
+
+Follow the directions on the page to determine where your Ollama Public Key is located.
+
+![Ollama Key](images/ollama-keys.png)
+
+Click on the `Add Ollama Public Key` button, and copy and paste the contents of your Ollama Public Key into the text field.
+
+To push a model to [ollama.com](https://ollama.com), first make sure that it is named correctly with your username. You may have to use the `ollama cp` command to copy
+your model to give it the correct name. Once you're happy with your model's name, use the `ollama push` command to push it to [ollama.com](https://ollama.com).
 
 ```shell
-$ ollama create mymodel
-transferring model data
-using autodetected template gemma-instruct
-creating new layer sha256:baa2a0edc27d19cc6b7537578a9a7ba1a4e3214dc185ed5ae43692b319af7b84
-creating new layer sha256:ba66c3309914dbef07e5149a648fd1877f030d337a4f240d444ea335008943cb
-writing manifest
-success
+ollama cp mymodel myuser/mymodel
+ollama push myuser/mymodel
+```
+
+Once your model has been pushed, other users can pull and run it by using the command:
+
+```shell
+ollama run myuser/mymodel
 ```
 
-Defining a template in the Modelfile will disable this feature which may be useful if you want to use a different template than the autodetected one.

From 3eb08377f82a7df091df5a9e6e73fac0ed3bcc26 Mon Sep 17 00:00:00 2001
From: Michael Yang 
Date: Mon, 26 Aug 2024 16:36:50 -0700
Subject: [PATCH 285/384] detect chat template from configs that contain lists

---
 convert/tokenizer.go      | 17 ++++++-
 convert/tokenizer_test.go | 96 +++++++++++++++++++++++++++++++++++++++
 2 files changed, 111 insertions(+), 2 deletions(-)
 create mode 100644 convert/tokenizer_test.go

diff --git a/convert/tokenizer.go b/convert/tokenizer.go
index 653df6d28..429d36e7c 100644
--- a/convert/tokenizer.go
+++ b/convert/tokenizer.go
@@ -100,8 +100,21 @@ func parseTokenizer(fsys fs.FS, specialTokenTypes []string) (*Tokenizer, error)
 		}
 
 		if template, ok := p["chat_template"]; ok {
-			if err := json.Unmarshal(template, &t.Template); err != nil {
-				return nil, err
+			var s []struct {
+				Name     string `json:"name"`
+				Template string `json:"template"`
+			}
+			if err := json.Unmarshal(template, &t.Template); err == nil {
+				// noop
+			} else if err := json.Unmarshal(template, &s); err == nil {
+				for _, e := range s {
+					if e.Name == "default" {
+						t.Template = e.Template
+						break
+					}
+				}
+			} else {
+				return nil, fmt.Errorf("invalid chat_template: %w", err)
 			}
 		}
 
diff --git a/convert/tokenizer_test.go b/convert/tokenizer_test.go
new file mode 100644
index 000000000..ed0175a42
--- /dev/null
+++ b/convert/tokenizer_test.go
@@ -0,0 +1,96 @@
+package convert
+
+import (
+	"io"
+	"io/fs"
+	"os"
+	"path/filepath"
+	"strings"
+	"testing"
+
+	"github.com/google/go-cmp/cmp"
+)
+
+func createTokenizerFS(t *testing.T, dir string, files map[string]io.Reader) fs.FS {
+	t.Helper()
+
+	for k, v := range files {
+		if err := func() error {
+			f, err := os.Create(filepath.Join(dir, k))
+			if err != nil {
+				return err
+			}
+			defer f.Close()
+
+			if _, err := io.Copy(f, v); err != nil {
+				return err
+			}
+
+			return nil
+		}(); err != nil {
+			t.Fatalf("unexpected error: %v", err)
+		}
+	}
+
+	return os.DirFS(dir)
+}
+
+func TestParseTokenizer(t *testing.T) {
+	cases := []struct {
+		name              string
+		fsys              fs.FS
+		specialTokenTypes []string
+		want              *Tokenizer
+	}{
+		{
+			name: "string chat template",
+			fsys: createTokenizerFS(t, t.TempDir(), map[string]io.Reader{
+				"tokenizer.json": strings.NewReader(`{}`),
+				"tokenizer_config.json": strings.NewReader(`{
+					"chat_template": ""
+				}`),
+			}),
+			want: &Tokenizer{
+				Vocabulary: &Vocabulary{Model: "gpt2"},
+				Pre:        "default",
+				Template:   "",
+			},
+		},
+		{
+			name: "list chat template",
+			fsys: createTokenizerFS(t, t.TempDir(), map[string]io.Reader{
+				"tokenizer.json": strings.NewReader(`{}`),
+				"tokenizer_config.json": strings.NewReader(`{
+					"chat_template": [
+						{
+							"name": "default",
+							"template": ""
+						},
+						{
+							"name": "tools",
+							"template": ""
+						}
+					]
+				}`),
+			}),
+			want: &Tokenizer{
+				Vocabulary: &Vocabulary{Model: "gpt2"},
+				Pre:        "default",
+				Template:   "",
+			},
+		},
+	}
+
+	for _, tt := range cases {
+		t.Run(tt.name, func(t *testing.T) {
+			tokenizer, err := parseTokenizer(tt.fsys, tt.specialTokenTypes)
+			if err != nil {
+				t.Fatalf("unexpected error: %v", err)
+			}
+
+			if diff := cmp.Diff(tt.want, tokenizer); diff != "" {
+				t.Errorf("unexpected tokenizer (-want +got):\n%s", diff)
+			}
+		})
+	}
+}

From eae3af6807954c10f7a95c4c1bfb536330270a23 Mon Sep 17 00:00:00 2001
From: Michael Yang 
Date: Tue, 27 Aug 2024 10:45:39 -0700
Subject: [PATCH 286/384] clean up convert tokenizer

---
 convert/convert_test.go | 2 +-
 convert/tokenizer.go    | 3 +--
 2 files changed, 2 insertions(+), 3 deletions(-)

diff --git a/convert/convert_test.go b/convert/convert_test.go
index 56b34f225..7dd489e2f 100644
--- a/convert/convert_test.go
+++ b/convert/convert_test.go
@@ -89,7 +89,7 @@ func TestMain(m *testing.M) {
 	os.Exit(m.Run())
 }
 
-func TestConvertFull(t *testing.T) {
+func TestConvertModel(t *testing.T) {
 	cases := []string{
 		"Meta-Llama-3-8B-Instruct",
 		"Meta-Llama-3.1-8B-Instruct",
diff --git a/convert/tokenizer.go b/convert/tokenizer.go
index 429d36e7c..14d6ba66c 100644
--- a/convert/tokenizer.go
+++ b/convert/tokenizer.go
@@ -154,7 +154,6 @@ func parseTokenizer(fsys fs.FS, specialTokenTypes []string) (*Tokenizer, error)
 }
 
 type tokenizer struct {
-	Version     string  `json:"version"`
 	AddedTokens []token `json:"added_tokens"`
 	Model       struct {
 		Type   string         `json:"type"`
@@ -252,7 +251,7 @@ func parseVocabulary(fsys fs.FS) (*Vocabulary, error) {
 		return pattern.Func(fsys)
 	}
 
-	return nil, errors.New("unknown tensor format")
+	return nil, errors.New("unknown tokenizer format")
 }
 
 type SpecialVocabulary struct {

From 1c70a00f716ed61c5b0a9e0f2a01876de0fc54d0 Mon Sep 17 00:00:00 2001
From: Patrick Devine 
Date: Tue, 27 Aug 2024 11:15:25 -0700
Subject: [PATCH 287/384] adjust image sizes

---
 docs/import.md | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/docs/import.md b/docs/import.md
index baf345506..a81c4bb42 100644
--- a/docs/import.md
+++ b/docs/import.md
@@ -158,7 +158,7 @@ You can share any model you have created by pushing it to [ollama.com](https://o
 
 First, use your browser to go to the [Ollama Sign-Up](https://ollama.com/signup) page. If you already have an account, you can skip this step.
 
-![Sign-Up](images/signup.png)
+Sign-Up
 
 The `Username` field will be used as part of your model's name (e.g. `jmorganca/mymodel`), so make sure you are comfortable with the username that you have selected.
 
@@ -166,7 +166,7 @@ Now that you have created an account and are signed-in, go to the [Ollama Keys S
 
 Follow the directions on the page to determine where your Ollama Public Key is located.
 
-![Ollama Key](images/ollama-keys.png)
+Ollama Keys
 
 Click on the `Add Ollama Public Key` button, and copy and paste the contents of your Ollama Public Key into the text field.
 

From 397cae79620e09220f4207beadd523ce1ae7cbd5 Mon Sep 17 00:00:00 2001
From: Sean Khatiri <39913795+seankhatiri@users.noreply.github.com>
Date: Tue, 27 Aug 2024 16:28:29 -0400
Subject: [PATCH 288/384] llm: fix typo in comment (#6530)

---
 llm/server.go | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/llm/server.go b/llm/server.go
index 4e5dac283..c38bc6bb8 100644
--- a/llm/server.go
+++ b/llm/server.go
@@ -409,7 +409,7 @@ func NewLlamaServer(gpus gpu.GpuInfoList, model string, ggml *GGML, adapters, pr
 		}
 
 		if err = s.cmd.Start(); err != nil {
-			// Detect permission denied and augment them essage about noexec
+			// Detect permission denied and augment the message about noexec
 			if errors.Is(err, os.ErrPermission) {
 				finalErr = fmt.Errorf("unable to start server %w.  %s may have noexec set.  Set OLLAMA_TMPDIR for server to a writable executable directory", err, dir)
 				continue

From 4e1c4f6e0bfc7b0feabe08d7aa4bf92f444fbe66 Mon Sep 17 00:00:00 2001
From: Daniel Hiltgen 
Date: Tue, 27 Aug 2024 13:42:28 -0700
Subject: [PATCH 289/384] Update manual instructions with discrete ROCm bundle
 (#6445)

---
 docs/linux.md | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/docs/linux.md b/docs/linux.md
index d1d5892c4..fbaf48454 100644
--- a/docs/linux.md
+++ b/docs/linux.md
@@ -28,6 +28,11 @@ Download and extract the Linux package:
 curl -fsSL https://ollama.com/download/ollama-linux-amd64.tgz | sudo tar zx -C /usr
 ```
 
+If you have an AMD GPU, also download and extract the ROCm package into the same location
+```bash
+curl -fsSL https://ollama.com/download/ollama-linux-amd64-rocm.tgz | sudo tar zx -C /usr
+```
+
 ### Adding Ollama as a startup service (recommended)
 
 Create a user for Ollama:

From 1713eddcd05e4c234108511f6fc1470c383e21b2 Mon Sep 17 00:00:00 2001
From: Patrick Devine 
Date: Tue, 27 Aug 2024 14:19:47 -0700
Subject: [PATCH 290/384] Fix import image width (#6528)

---
 docs/images/ollama-keys.png | Bin 144101 -> 154049 bytes
 docs/import.md              |   6 +++---
 2 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/docs/images/ollama-keys.png b/docs/images/ollama-keys.png
index 119a4bcf4cb0cf0639f3e71e3cb70010f8179316..942079a8637c6e1312c30f19a688e115fda93829 100644
GIT binary patch
literal 154049
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

!pwP8H(-MhL|l_2v~BpuT+LXgK7$OKKh;u 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 TE)4q`@J~ZkSEW+PBKUs+^5v58 literal 144101 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 +Sign-Up The `Username` field will be used as part of your model's name (e.g. `jmorganca/mymodel`), so make sure you are comfortable with the username that you have selected. @@ -166,7 +166,7 @@ Now that you have created an account and are signed-in, go to the [Ollama Keys S Follow the directions on the page to determine where your Ollama Public Key is located. -Ollama Keys +Ollama Keys Click on the `Add Ollama Public Key` button, and copy and paste the contents of your Ollama Public Key into the text field. From d13c3daa0bff581532b29c269040338fdc895aeb Mon Sep 17 00:00:00 2001 From: Patrick Devine Date: Tue, 27 Aug 2024 14:46:47 -0700 Subject: [PATCH 291/384] add safetensors to the modelfile docs (#6532) --- docs/modelfile.md | 45 ++++++++++++++++++++++++++++++++++++++------- 1 file changed, 38 insertions(+), 7 deletions(-) diff --git a/docs/modelfile.md b/docs/modelfile.md index 852bf96c1..51827e749 100644 --- a/docs/modelfile.md +++ b/docs/modelfile.md @@ -11,8 +11,9 @@ A model file is the blueprint to create and share models with Ollama. - [Examples](#examples) - [Instructions](#instructions) - [FROM (Required)](#from-required) - - [Build from llama3](#build-from-llama3) - - [Build from a bin file](#build-from-a-bin-file) + - [Build from llama3.1](#build-from-llama31) + - [Build from a Safetensors model](#build-from-a-safetensors-model) + - [Build from a GGUF file](#build-from-a-gguf-file) - [PARAMETER](#parameter) - [Valid Parameters and Values](#valid-parameters-and-values) - [TEMPLATE](#template) @@ -99,22 +100,39 @@ The `FROM` instruction defines the base model to use when creating a model. FROM : ``` -#### Build from llama3 +#### Build from llama3.1 ```modelfile -FROM llama3 +FROM llama3.1 ``` A list of available base models: +Additional models can be found at: + -#### Build from a `bin` file +#### Build from a Safetensors model + +```modelfile +FROM +``` + +The model directory should contain the Safetensors weights for a supported architecture. + +Currently supported model architectures: + * Llama (including Llama 2, Llama 3, and Llama 3.1) + * Mistral (including Mistral 1, Mistral 2, and Mixtral) + * Gemma (including Gemma 1 and Gemma 2) + * Phi3 + +#### Build from a GGUF file ```modelfile FROM ./ollama-model.bin ``` -This bin file location should be specified as an absolute path or relative to the `Modelfile` location. +The GGUF bin file location should be specified as an absolute path or relative to the `Modelfile` location. + ### PARAMETER @@ -174,7 +192,20 @@ SYSTEM """""" ### ADAPTER -The `ADAPTER` instruction is an optional instruction that specifies any LoRA adapter that should apply to the base model. The value of this instruction should be an absolute path or a path relative to the Modelfile and the file must be in a GGML file format. The adapter should be tuned from the base model otherwise the behaviour is undefined. +The `ADAPTER` instruction specifies a fine tuned LoRA adapter that should apply to the base model. The value of the adapter should be an absolute path or a path relative to the Modelfile. The base model should be specified with a `FROM` instruction. If the base model is not the same as the base model that the adapter was tuned from the behaviour will be erratic. + +#### Safetensor adapter + +```modelfile +ADAPTER +``` + +Currently supported Safetensor adapters: + * Llama (including Llama 2, Llama 3, and Llama 3.1) + * Mistral (including Mistral 1, Mistral 2, and Mixtral) + * Gemma (including Gemma 1 and Gemma 2) + +#### GGUF adapter ```modelfile ADAPTER ./ollama-lora.bin From 60e47573a6efef0c9d13a2970a3951d739b9304e Mon Sep 17 00:00:00 2001 From: Michael Yang Date: Tue, 27 Aug 2024 11:11:53 -0700 Subject: [PATCH 292/384] more tokenizer tests --- convert/tokenizer_test.go | 112 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 112 insertions(+) diff --git a/convert/tokenizer_test.go b/convert/tokenizer_test.go index ed0175a42..d9550e095 100644 --- a/convert/tokenizer_test.go +++ b/convert/tokenizer_test.go @@ -79,6 +79,118 @@ func TestParseTokenizer(t *testing.T) { Template: "", }, }, + { + name: "added tokens", + fsys: createTokenizerFS(t, t.TempDir(), map[string]io.Reader{ + "tokenizer.json": strings.NewReader(`{ + "added_tokens": [ + { + "id": 999, + "content": "", + "special": false + } + ] + }`), + }), + want: &Tokenizer{ + Vocabulary: &Vocabulary{ + Model: "gpt2", + Tokens: []string{""}, + Scores: []float32{999}, + Types: []int32{4}, + }, + Pre: "default", + }, + }, + { + name: "added tokens overlap vocab", + fsys: createTokenizerFS(t, t.TempDir(), map[string]io.Reader{ + "tokenizer.json": strings.NewReader(`{ + "added_tokens": [ + { + "id": 0, + "content": "", + "special": true + } + ], + "model": { + "vocab": { + "": 0 + } + } + }`), + }), + want: &Tokenizer{ + Vocabulary: &Vocabulary{ + Model: "gpt2", + Tokens: []string{""}, + Scores: []float32{0}, + Types: []int32{3}, + }, + Pre: "default", + }, + }, + { + name: "special token types", + fsys: createTokenizerFS(t, t.TempDir(), map[string]io.Reader{ + "tokenizer.json": strings.NewReader(`{ + "added_tokens": [ + { + "id": 0, + "content": "", + "special": true + }, + { + "id": 1, + "content": "", + "special": true + }, + { + "id": 2, + "content": "", + "special": true + }, + { + "id": 3, + "content": "", + "special": true + } + ], + "model": { + "vocab": { + "": 0, + "": 1, + "": 2, + "": 3 + } + } + }`), + "tokenizer_config.json": strings.NewReader(`{ + "add_bos_token": true, + "add_eos_token": false, + "bos_token": "", + "eos_token": "", + "pad_token": "", + "unk_token": "" + }`), + }), + specialTokenTypes: []string{"pad", "eos", "bos", "unk"}, + want: &Tokenizer{ + Vocabulary: &Vocabulary{ + Model: "gpt2", + Tokens: []string{"", "", "", ""}, + Scores: []float32{0, 1, 2, 3}, + Types: []int32{3, 3, 3, 3}, + }, + SpecialVocabulary: []*SpecialVocabulary{ + {Type: "pad", Content: "", ID: 0, AddToken: false}, + {Type: "eos", Content: "", ID: 1, AddToken: false}, + {Type: "bos", Content: "", ID: 2, AddToken: true}, + {Type: "unk", Content: "", ID: 3, AddToken: false}, + }, + Pre: "default", + }, + }, } for _, tt := range cases { From 413ae39f3c947a65f9c940edc0319218135ba767 Mon Sep 17 00:00:00 2001 From: Michael Yang Date: Tue, 27 Aug 2024 11:34:30 -0700 Subject: [PATCH 293/384] update templates to use messages --- server/routes_create_test.go | 4 ++-- template/alfred.gotmpl | 3 ++- template/alpaca.gotmpl | 20 +++++++++++++++----- template/chatml.gotmpl | 7 ++----- template/chatqa.gotmpl | 11 ++++++----- template/codellama-70b-instruct.gotmpl | 16 ++++++++-------- template/falcon-instruct.gotmpl | 11 +++++++---- template/gemma-instruct.gotmpl | 21 ++++++++++++++++----- template/granite-instruct.gotmpl | 13 ++++++------- template/llama2-chat.gotmpl | 18 +++++++++++++----- template/llama3-instruct.gotmpl | 8 +++----- template/magicoder.gotmpl | 19 ++++++++++++++----- template/mistral-instruct.gotmpl | 7 +++++-- template/openchat.gotmpl | 7 ++++++- template/phi-3.gotmpl | 7 ++----- template/solar-instruct.gotmpl | 14 ++++++++------ template/starcoder2-instruct.gotmpl | 20 +++++++++++++++----- template/vicuna.gotmpl | 16 +++++++++++++--- template/zephyr.gotmpl | 7 ++----- 19 files changed, 145 insertions(+), 84 deletions(-) diff --git a/server/routes_create_test.go b/server/routes_create_test.go index 4de07b252..2f577eb4c 100644 --- a/server/routes_create_test.go +++ b/server/routes_create_test.go @@ -593,9 +593,9 @@ func TestCreateDetectTemplate(t *testing.T) { checkFileExists(t, filepath.Join(p, "blobs", "*"), []string{ filepath.Join(p, "blobs", "sha256-0d79f567714c62c048378f2107fb332dabee0135d080c302d884317da9433cc5"), + filepath.Join(p, "blobs", "sha256-35360843d0c84fb1506952a131bbef13cd2bb4a541251f22535170c05b56e672"), filepath.Join(p, "blobs", "sha256-553c4a3f747b3d22a4946875f1cc8ed011c2930d83f864a0c7265f9ec0a20413"), - filepath.Join(p, "blobs", "sha256-c608dc615584cd20d9d830363dabf8a4783ae5d34245c3d8c115edb3bc7b28e4"), - filepath.Join(p, "blobs", "sha256-ea34c57ba5b78b740aafe2aeb74dc6507fc3ad14170b64c26a04fb9e36c88d75"), + filepath.Join(p, "blobs", "sha256-de3959f841e9ef6b4b6255fa41cb9e0a45da89c3066aa72bdd07a4747f848990"), }) }) diff --git a/template/alfred.gotmpl b/template/alfred.gotmpl index cecb9d2c8..86dba48f2 100644 --- a/template/alfred.gotmpl +++ b/template/alfred.gotmpl @@ -1 +1,2 @@ -{{ if .System }}{{ .System }}{{ end }}{{ if .Prompt }}{{ .Prompt }}{{ end }}{{ .Response }} \ No newline at end of file +{{- range .Messages }}{{ .Content }} +{{- end }} \ No newline at end of file diff --git a/template/alpaca.gotmpl b/template/alpaca.gotmpl index ec7a8edcb..004397367 100644 --- a/template/alpaca.gotmpl +++ b/template/alpaca.gotmpl @@ -1,8 +1,18 @@ -{{ if .System }}{{ .System }} +{{- $system := "" }} +{{- range .Messages }} +{{- if eq .Role "system" }} +{{- if not $system }}{{ $system = .Content }} +{{- else }}{{ $system = printf "%s\n\n%s" $system .Content }} +{{- end }} +{{- else if eq .Role "user" }} +{{- if $system }}{{ $system }} -{{ end }}{{ if .Prompt }}### Instruction: -{{ .Prompt }} +{{ $system = "" }} +{{- end }}### Instruction: +{{ .Content }} -{{ end }}### Response: -{{ .Response }} +{{ else if eq .Role "assistant" }}### Response: +{{ .Content }} +{{ end }} +{{- end }}### Response: diff --git a/template/chatml.gotmpl b/template/chatml.gotmpl index fb672601a..43207ab14 100644 --- a/template/chatml.gotmpl +++ b/template/chatml.gotmpl @@ -1,6 +1,3 @@ -{{ if .System }}<|im_start|>system -{{ .System }}<|im_end|> -{{ end }}{{ if .Prompt }}<|im_start|>user -{{ .Prompt }}<|im_end|> +{{- range .Messages }}<|im_start|>{{ .Role }} +{{ .Content }}<|im_end|> {{ end }}<|im_start|>assistant -{{ .Response }}<|im_end|> diff --git a/template/chatqa.gotmpl b/template/chatqa.gotmpl index 91679a72d..0f91e0f01 100644 --- a/template/chatqa.gotmpl +++ b/template/chatqa.gotmpl @@ -1,6 +1,7 @@ -{{ if .System }}System: {{ .System }} - -{{ end }}{{ if .Prompt }}User: {{ .Prompt }} - -{{ end }}Assistant: {{ .Response }} +{{- range .Messages }} +{{- if eq .Role "system" }}System: +{{- else if eq .Role "user" }}User: +{{- else if eq .Role "assistant" }}Assistant: +{{- end }} {{ .Content }} +{{ end }}Assistant: \ No newline at end of file diff --git a/template/codellama-70b-instruct.gotmpl b/template/codellama-70b-instruct.gotmpl index e5856042c..189315205 100644 --- a/template/codellama-70b-instruct.gotmpl +++ b/template/codellama-70b-instruct.gotmpl @@ -1,10 +1,10 @@ -{{ if .System }}Source: system - - {{ .System }} {{ end }}Source: user - - {{ .Prompt }} Source: assistant -{{- if not .Response }} -Destination: user +{{- range .Messages }}Source: +{{- if eq .Role "system" }} system +{{- else if eq .Role "user" }} user +{{- else if eq .Role "assistant" }} assistant {{- end }} - {{ .Response }} \ No newline at end of file + {{ .Content }} {{ end }}Source: assistant +Destination: user + + \ No newline at end of file diff --git a/template/falcon-instruct.gotmpl b/template/falcon-instruct.gotmpl index 0a5fe48e8..b9b51d2c6 100644 --- a/template/falcon-instruct.gotmpl +++ b/template/falcon-instruct.gotmpl @@ -1,5 +1,8 @@ -{{ if .System }}System: {{ .System }} -{{ end }}{{ if .Prompt }}User: -{{ .Prompt }} +{{- range .Messages }} +{{- if eq .Role "system" }}System: {{ .Content }} +{{ continue }} +{{- else if eq .Role "user" }}User: +{{- else if eq .Role "assistant" }}Falcon: +{{- end }} +{{ .Content }} {{ end }}Falcon: -{{ .Response }} diff --git a/template/gemma-instruct.gotmpl b/template/gemma-instruct.gotmpl index 3c3a84256..cce257192 100644 --- a/template/gemma-instruct.gotmpl +++ b/template/gemma-instruct.gotmpl @@ -1,5 +1,16 @@ -user -{{ if .System }}{{ .System }} -{{ end }}{{ .Prompt }} -model -{{ .Response }} +{{- $system := "" }} +{{- range .Messages }} +{{- if eq .Role "system" }} +{{- if not $system }}{{ $system = .Content }} +{{- else }}{{ $system = printf "%s\n\n%s" $system .Content }} +{{- end }} +{{- continue }} +{{- else if eq .Role "user" }}user +{{- if $system }} +{{ $system }} +{{- $system = "" }} +{{- end }} +{{- else if eq .Role "assistant" }}model +{{- end }} +{{ .Content }} +{{ end }}model diff --git a/template/granite-instruct.gotmpl b/template/granite-instruct.gotmpl index 56690fce6..83634990e 100644 --- a/template/granite-instruct.gotmpl +++ b/template/granite-instruct.gotmpl @@ -1,9 +1,8 @@ -{{ if .System }}System: -{{ .System }} - -{{ end }}{{ if .Prompt }}Question: -{{ .Prompt }} +{{- range .Messages }} +{{- if eq .Role "system" }}System: +{{- else if eq .Role "user" }}Question: +{{- else if eq .Role "assistant" }}Answer: +{{- end }} +{{ .Content }} {{ end }}Answer: -{{ .Response }} - diff --git a/template/llama2-chat.gotmpl b/template/llama2-chat.gotmpl index 013b414e2..5634a0720 100644 --- a/template/llama2-chat.gotmpl +++ b/template/llama2-chat.gotmpl @@ -1,6 +1,14 @@ -[INST] <> -{{- if .System }} -{{ .System }} -{{ end }}<> +{{- $system := "" }}[INST] {{ range .Messages }} +{{- if eq .Role "system" }} +{{- if not $system }}{{ $system = .Content }} +{{- else }}{{ $system = printf "%s\n\n%s" $system .Content }} +{{- end }} +{{- else if eq .Role "user" }}<> +{{- if $system }} +{{ $system }} +{{ $system = "" }} +{{- end }}<> -{{ .Prompt }} [/INST] {{ .Response }} \ No newline at end of file +{{ .Content }} [/INST] +{{- else if eq .Role "assistant" }} {{ .Content }}[INST] {{ end }} +{{- end }} \ No newline at end of file diff --git a/template/llama3-instruct.gotmpl b/template/llama3-instruct.gotmpl index 36d0218b6..305ae403b 100644 --- a/template/llama3-instruct.gotmpl +++ b/template/llama3-instruct.gotmpl @@ -1,7 +1,5 @@ -{{ if .System }}<|start_header_id|>system<|end_header_id|> +{{- range .Messages }}<|start_header_id|>{{ .Role }}<|end_header_id|> -{{ .System }}<|eot_id|>{{ end }}{{ if .Prompt }}<|start_header_id|>user<|end_header_id|> +{{ .Content }}<|eot_id|> +{{- end }}<|start_header_id|>assistant<|end_header_id|> -{{ .Prompt }}<|eot_id|>{{ end }}<|start_header_id|>assistant<|end_header_id|> - -{{ .Response }}<|eot_id|> \ No newline at end of file diff --git a/template/magicoder.gotmpl b/template/magicoder.gotmpl index 52abc01aa..e5ee0e470 100644 --- a/template/magicoder.gotmpl +++ b/template/magicoder.gotmpl @@ -1,8 +1,17 @@ -{{ if .System }}{{ .System }} +{{- $system := "" }} +{{- range .Messages }} +{{- if eq .Role "system" }} +{{- if not $system }}{{ $system = .Content }} +{{- else }}{{ $system = printf "%s\n\n%s" $system .Content }} +{{- end }} +{{- continue }} +{{- else if eq .Role "user" }} +{{- if $system }}{{ $system }} -{{ end }}{{ if .Prompt }}@@ Instruction -{{ .Prompt }} +{{ $system = "" }} +{{- end }}@@ Instruction +{{- else if eq .Role "assistant" }}@@ Response +{{- end }} +{{ .Content }} {{ end }}@@ Response -{{ .Response }} - diff --git a/template/mistral-instruct.gotmpl b/template/mistral-instruct.gotmpl index e489bd4c5..7a6ecdfd8 100644 --- a/template/mistral-instruct.gotmpl +++ b/template/mistral-instruct.gotmpl @@ -1,3 +1,6 @@ -[INST] {{ if .System }}{{ .System }} +[INST] {{ range $index, $_ := .Messages }} +{{- if eq .Role "system" }}{{ .Content }} -{{ end }}{{ .Prompt }}[/INST] {{ .Response }} \ No newline at end of file +{{ else if eq .Role "user" }}{{ .Content }}[/INST] +{{- else if eq .Role "assistant" }} {{ .Content }}[INST] {{ end }} +{{- end }} \ No newline at end of file diff --git a/template/openchat.gotmpl b/template/openchat.gotmpl index 9c1838343..66a4d687b 100644 --- a/template/openchat.gotmpl +++ b/template/openchat.gotmpl @@ -1 +1,6 @@ -{{ if .System }}GPT4 Correct System: {{ .System }}<|end_of_turn|>{{ end }}GPT4 Correct User: {{ .Prompt }}<|end_of_turn|>GPT4 Correct Assistant: {{ .Response }}<|end_of_turn|> \ No newline at end of file +{{- range .Messages }}GPT4 Correct +{{- if eq .Role "system" }} System: +{{- else if eq .Role "user" }} User: +{{- else if eq .Role "assistant" }} Assistant: +{{- end }} {{ .Content }}<|end_of_turn|> +{{- end }}GPT4 Correct Assistant: \ No newline at end of file diff --git a/template/phi-3.gotmpl b/template/phi-3.gotmpl index 6c3610dda..abec2137d 100644 --- a/template/phi-3.gotmpl +++ b/template/phi-3.gotmpl @@ -1,6 +1,3 @@ -{{ if .System }}<|system|> -{{ .System }}<|end|> -{{ end }}{{ if .Prompt }}<|user|> -{{ .Prompt }}<|end|> +{{- range .Messages }}<|{{ .Role }}|> +{{ .Content }}<|end|> {{ end }}<|assistant|> -{{ .Response }}<|end|> diff --git a/template/solar-instruct.gotmpl b/template/solar-instruct.gotmpl index 1c14960d4..263bde804 100644 --- a/template/solar-instruct.gotmpl +++ b/template/solar-instruct.gotmpl @@ -1,9 +1,11 @@ -{{ if .System }}### System: -{{ .System }} +{{- range .Messages }} +{{- if eq .Role "system" }}### System: +{{- else if eq .Role "user" }}### User: +{{- else if eq .Role "assistant" }}### Assistant: +{{ .Content }} -{{ end }}{{ if .Prompt }}### User: -{{ .Prompt }} +{{ continue }} +{{- end }} +{{ .Content }} {{ end }}### Assistant: -{{ .Response }} - diff --git a/template/starcoder2-instruct.gotmpl b/template/starcoder2-instruct.gotmpl index 6c93a7abc..7963b4f9a 100644 --- a/template/starcoder2-instruct.gotmpl +++ b/template/starcoder2-instruct.gotmpl @@ -1,8 +1,18 @@ -{{ if .System }}{{ .System }} +{{- $system := "" }} +{{- range .Messages }} +{{- if eq .Role "system" }} +{{- if not $system }}{{ $system = .Content }} +{{- else }}{{ $system = printf "%s\n\n%s" $system .Content }} +{{- end }} +{{- else if eq .Role "user" }} +{{- if $system }}{{ $system }} -{{ end }}{{ if .Prompt }}### Instruction -{{ .Prompt }} +{{ $system = "" }} +{{- end }}### Instruction +{{ .Content }} -{{ end }}### Response -{{ .Response }}<|endoftext|> +{{ else if eq .Role "assistant" }}### Response +{{ .Content }}<|endoftext|> +{{ end }} +{{- end }}### Response diff --git a/template/vicuna.gotmpl b/template/vicuna.gotmpl index 515b2fe94..c27f39c52 100644 --- a/template/vicuna.gotmpl +++ b/template/vicuna.gotmpl @@ -1,4 +1,14 @@ -{{ if .System }}{{ .System }} +{{- $system := "" }} +{{- range .Messages }} +{{- if eq .Role "system" }} +{{- if not $system }}{{ $system = .Content }} +{{- else }}{{ $system = printf "%s\n\n%s" $system .Content }} +{{- end }} +{{- else if eq .Role "user" }} +{{- if $system }}{{ $system }} -{{ end }}{{ if .Prompt }}USER: {{ .Prompt }} -{{ end }}ASSISTANT: {{ .Response }} +{{ $system = "" }} +{{- end }}USER: {{ .Content }} +{{ else if eq .Role "assistant" }}ASSISTANT: {{ .Content }} +{{ end }} +{{- end }}ASSISTANT: \ No newline at end of file diff --git a/template/zephyr.gotmpl b/template/zephyr.gotmpl index 1f889f267..25da148a0 100644 --- a/template/zephyr.gotmpl +++ b/template/zephyr.gotmpl @@ -1,6 +1,3 @@ -{{ if .System }}<|system|> -{{ .System }} -{{ end }}{{ if .Prompt }}<|user|> -{{ .Prompt }} +{{- range .Messages }}<|{{ .Role }}|> +{{ .Content }} {{ end }}<|assistant|> -{{ .Response }} From 93ea9240aee8a51b0fe455fdddedec2046438f95 Mon Sep 17 00:00:00 2001 From: Daniel Hiltgen Date: Tue, 27 Aug 2024 16:19:00 -0700 Subject: [PATCH 294/384] Move ollama executable out of bin dir (#6535) --- app/ollama.iss | 8 ++++---- envconfig/config.go | 11 ++++++++++- gpu/amd_common.go | 4 +++- gpu/amd_windows.go | 2 +- gpu/gpu.go | 2 +- scripts/build_windows.ps1 | 4 ++-- 6 files changed, 21 insertions(+), 10 deletions(-) diff --git a/app/ollama.iss b/app/ollama.iss index bce0a337e..34cc5c4cd 100644 --- a/app/ollama.iss +++ b/app/ollama.iss @@ -87,7 +87,7 @@ DialogFontSize=12 [Files] Source: ".\app.exe"; DestDir: "{app}"; DestName: "{#MyAppExeName}" ; Flags: ignoreversion 64bit -Source: "..\ollama.exe"; DestDir: "{app}\bin"; Flags: ignoreversion 64bit +Source: "..\ollama.exe"; DestDir: "{app}"; Flags: ignoreversion 64bit Source: "..\dist\windows-{#ARCH}\lib\ollama\runners\*"; DestDir: "{app}\lib\ollama\runners"; Flags: ignoreversion 64bit recursesubdirs Source: "..\dist\ollama_welcome.ps1"; DestDir: "{app}"; Flags: ignoreversion Source: ".\assets\app.ico"; DestDir: "{app}"; Flags: ignoreversion @@ -99,7 +99,7 @@ Name: "{userstartup}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; IconFilen Name: "{userprograms}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; IconFilename: "{app}\app.ico" [Run] -Filename: "{cmd}"; Parameters: "/C set PATH={app}\bin;%PATH% & ""{app}\{#MyAppExeName}"""; Flags: postinstall nowait runhidden +Filename: "{cmd}"; Parameters: "/C set PATH={app};%PATH% & ""{app}\{#MyAppExeName}"""; Flags: postinstall nowait runhidden [UninstallRun] ; Filename: "{cmd}"; Parameters: "/C ""taskkill /im ''{#MyAppExeName}'' /f /t"; Flags: runhidden @@ -134,8 +134,8 @@ SetupAppRunningError=Another Ollama installer is running.%n%nPlease cancel or fi [Registry] Root: HKCU; Subkey: "Environment"; \ - ValueType: expandsz; ValueName: "Path"; ValueData: "{olddata};{app}\bin"; \ - Check: NeedsAddPath('{app}\bin') + ValueType: expandsz; ValueName: "Path"; ValueData: "{olddata};{app}"; \ + Check: NeedsAddPath('{app}') [Code] diff --git a/envconfig/config.go b/envconfig/config.go index 7e45a4f51..806a2d08f 100644 --- a/envconfig/config.go +++ b/envconfig/config.go @@ -190,7 +190,7 @@ func RunnersDir() (p string) { } var paths []string - for _, root := range []string{filepath.Dir(exe), filepath.Join(filepath.Dir(exe), ".."), cwd} { + for _, root := range []string{filepath.Dir(exe), filepath.Join(filepath.Dir(exe), LibRelativeToExe()), cwd} { paths = append(paths, root, filepath.Join(root, runtime.GOOS+"-"+runtime.GOARCH), @@ -282,3 +282,12 @@ func Values() map[string]string { func Var(key string) string { return strings.Trim(strings.TrimSpace(os.Getenv(key)), "\"'") } + +// On windows, we keep the binary at the top directory, but +// other platforms use a "bin" directory, so this returns ".." +func LibRelativeToExe() string { + if runtime.GOOS == "windows" { + return "." + } + return ".." +} diff --git a/gpu/amd_common.go b/gpu/amd_common.go index 72d204f77..2894ac2c4 100644 --- a/gpu/amd_common.go +++ b/gpu/amd_common.go @@ -9,6 +9,8 @@ import ( "path/filepath" "runtime" "strings" + + "github.com/ollama/ollama/envconfig" ) // Determine if the given ROCm lib directory is usable by checking for existence of some glob patterns @@ -54,7 +56,7 @@ func commonAMDValidateLibDir() (string, error) { // Installer payload location if we're running the installed binary exe, err := os.Executable() if err == nil { - rocmTargetDir := filepath.Join(filepath.Dir(exe), "..", "lib", "ollama") + rocmTargetDir := filepath.Join(filepath.Dir(exe), envconfig.LibRelativeToExe(), "lib", "ollama") if rocmLibUsable(rocmTargetDir) { slog.Debug("detected ROCM next to ollama executable " + rocmTargetDir) return rocmTargetDir, nil diff --git a/gpu/amd_windows.go b/gpu/amd_windows.go index a0ae7c960..ef6bf830c 100644 --- a/gpu/amd_windows.go +++ b/gpu/amd_windows.go @@ -153,7 +153,7 @@ func AMDValidateLibDir() (string, error) { // Installer payload (if we're running from some other location) localAppData := os.Getenv("LOCALAPPDATA") appDir := filepath.Join(localAppData, "Programs", "Ollama") - rocmTargetDir := filepath.Join(appDir, "..", "lib", "ollama") + rocmTargetDir := filepath.Join(appDir, envconfig.LibRelativeToExe(), "lib", "ollama") if rocmLibUsable(rocmTargetDir) { slog.Debug("detected ollama installed ROCm at " + rocmTargetDir) return rocmTargetDir, nil diff --git a/gpu/gpu.go b/gpu/gpu.go index 10afb1e3f..3de93f7fc 100644 --- a/gpu/gpu.go +++ b/gpu/gpu.go @@ -653,7 +653,7 @@ func LibraryDir() string { slog.Warn("failed to lookup working directory", "error", err) } // Scan for any of our dependeices, and pick first match - for _, root := range []string{filepath.Dir(appExe), filepath.Join(filepath.Dir(appExe), ".."), cwd} { + for _, root := range []string{filepath.Dir(appExe), filepath.Join(filepath.Dir(appExe), envconfig.LibRelativeToExe()), cwd} { libDep := filepath.Join("lib", "ollama") if _, err := os.Stat(filepath.Join(root, libDep)); err == nil { return filepath.Join(root, libDep) diff --git a/scripts/build_windows.ps1 b/scripts/build_windows.ps1 index 9cebf1f40..eb8570c8f 100644 --- a/scripts/build_windows.ps1 +++ b/scripts/build_windows.ps1 @@ -122,8 +122,8 @@ function buildOllama() { /csp "Google Cloud KMS Provider" /kc ${env:KEY_CONTAINER} ollama.exe if ($LASTEXITCODE -ne 0) { exit($LASTEXITCODE)} } - New-Item -ItemType Directory -Path .\dist\windows-${script:TARGET_ARCH}\bin\ -Force - cp .\ollama.exe .\dist\windows-${script:TARGET_ARCH}\bin\ + New-Item -ItemType Directory -Path .\dist\windows-${script:TARGET_ARCH}\ -Force + cp .\ollama.exe .\dist\windows-${script:TARGET_ARCH}\ } function buildApp() { From 6c1c1ad6a90e8fe23d63d2c431745e48e3fe9d81 Mon Sep 17 00:00:00 2001 From: Patrick Devine Date: Tue, 27 Aug 2024 17:54:04 -0700 Subject: [PATCH 295/384] throw an error when encountering unsupport tensor sizes (#6538) --- convert/convert_test.go | 101 ++++++++++++++++++++++++++++++++++ convert/reader_safetensors.go | 5 ++ 2 files changed, 106 insertions(+) diff --git a/convert/convert_test.go b/convert/convert_test.go index 56b34f225..f71ff8cd7 100644 --- a/convert/convert_test.go +++ b/convert/convert_test.go @@ -140,6 +140,107 @@ func TestConvertFull(t *testing.T) { } } +func TestConvertInvalidDatatype(t *testing.T) { + f, err := os.CreateTemp(t.TempDir(), "testmodel") + if err != nil { + t.Fatal(err) + } + defer f.Close() + + tempDir := t.TempDir() + generateSafetensorTestData(t, tempDir) + + err = ConvertModel(os.DirFS(tempDir), f) + if err == nil || err.Error() != "unsupported safetensors model" { + t.Errorf("expected error but didn't get one") + } +} + +func generateSafetensorTestData(t *testing.T, tempDir string) { + type tensorData struct { + Offsets []int `json:"data_offsets"` + Type string `json:"dtype"` + Shape []int `json:"shape"` + } + offset := 4096 * 14336 + + td := map[string]*tensorData{} + td["model.layers.0.mlp.down_proj.weight"] = &tensorData{ + Offsets: []int{0, offset}, + Type: "I8", + Shape: []int{4096, 14336}, + } + td["model.layers.0.mlp.down_proj.weight_format"] = &tensorData{ + Offsets: []int{offset, offset}, + Type: "U8", + Shape: []int{}, + } + + data, err := json.Marshal(td) + if err != nil { + t.Fatal(err) + } + + var buf bytes.Buffer + + l := int64(len(data)) + err = binary.Write(&buf, binary.LittleEndian, l) + if err != nil { + t.Fatal(err) + } + + _, err = buf.Write(data) + if err != nil { + t.Fatal(err) + } + + fdata, err := os.Create(filepath.Join(tempDir, "model-00001-of-00001.safetensors")) + if err != nil { + t.Fatal(err) + } + defer fdata.Close() + + _, err = fdata.Write(buf.Bytes()) + if err != nil { + t.Fatal(err) + } + + configData := ` +{ + "architectures": [ + "LlamaForCausalLM" + ] +} +` + + f, err := os.Create(filepath.Join(tempDir, "config.json")) + if err != nil { + t.Fatal(err) + } + defer f.Close() + + _, err = f.WriteString(configData) + if err != nil { + t.Fatal(err) + } + + tokenizerData := ` +{ +} +` + + f, err = os.Create(filepath.Join(tempDir, "tokenizer.json")) + if err != nil { + t.Fatal(err) + } + defer f.Close() + + _, err = f.WriteString(tokenizerData) + if err != nil { + t.Fatal(err) + } +} + func TestConvertAdapter(t *testing.T) { type AdapterCase struct { Name string diff --git a/convert/reader_safetensors.go b/convert/reader_safetensors.go index 32a362cd9..e1dde8fab 100644 --- a/convert/reader_safetensors.go +++ b/convert/reader_safetensors.go @@ -4,6 +4,7 @@ import ( "bytes" "encoding/binary" "encoding/json" + "errors" "fmt" "io" "io/fs" @@ -50,6 +51,10 @@ func parseSafetensors(fsys fs.FS, replacer *strings.Replacer, ps ...string) ([]T for _, key := range keys { if value := headers[key]; value.Type != "" { + // bitsandbytes quantized models are unsupported + if len(value.Shape) == 0 { + return nil, errors.New("unsupported safetensors model") + } ts = append(ts, safetensor{ fs: fsys, path: p, From d9d50c43cca950e70cdd288d62c58a8d5a8e43d4 Mon Sep 17 00:00:00 2001 From: Michael Yang Date: Tue, 27 Aug 2024 17:56:04 -0700 Subject: [PATCH 296/384] validate model path --- server/modelpath.go | 18 +++++------------- server/modelpath_test.go | 8 ++++++++ 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/server/modelpath.go b/server/modelpath.go index 354eeed78..d498c4678 100644 --- a/server/modelpath.go +++ b/server/modelpath.go @@ -73,18 +73,6 @@ func ParseModelPath(name string) ModelPath { var errModelPathInvalid = errors.New("invalid model path") -func (mp ModelPath) Validate() error { - if mp.Repository == "" { - return fmt.Errorf("%w: model repository name is required", errModelPathInvalid) - } - - if strings.Contains(mp.Tag, ":") { - return fmt.Errorf("%w: ':' (colon) is not allowed in tag names", errModelPathInvalid) - } - - return nil -} - func (mp ModelPath) GetNamespaceRepository() string { return fmt.Sprintf("%s/%s", mp.Namespace, mp.Repository) } @@ -105,7 +93,11 @@ func (mp ModelPath) GetShortTagname() string { // GetManifestPath returns the path to the manifest file for the given model path, it is up to the caller to create the directory if it does not exist. func (mp ModelPath) GetManifestPath() (string, error) { - return filepath.Join(envconfig.Models(), "manifests", mp.Registry, mp.Namespace, mp.Repository, mp.Tag), nil + if p := filepath.Join(mp.Registry, mp.Namespace, mp.Repository, mp.Tag); filepath.IsLocal(p) { + return filepath.Join(envconfig.Models(), "manifests", p), nil + } + + return "", errModelPathInvalid } func (mp ModelPath) BaseURL() *url.URL { diff --git a/server/modelpath_test.go b/server/modelpath_test.go index 849e0fa73..ef26266bd 100644 --- a/server/modelpath_test.go +++ b/server/modelpath_test.go @@ -1,6 +1,7 @@ package server import ( + "errors" "os" "path/filepath" "testing" @@ -154,3 +155,10 @@ func TestParseModelPath(t *testing.T) { }) } } + +func TestInsecureModelpath(t *testing.T) { + mp := ParseModelPath("../../..:something") + if _, err := mp.GetManifestPath(); !errors.Is(err, errModelPathInvalid) { + t.Errorf("expected error: %v", err) + } +} From 8e6da3cbc57de9dce71bb6f0b13ab2929af1474f Mon Sep 17 00:00:00 2001 From: Michael Yang Date: Tue, 27 Aug 2024 17:57:34 -0700 Subject: [PATCH 297/384] update deprecated warnings --- .golangci.yaml | 4 ++++ api/types.go | 16 +++++++++------- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/.golangci.yaml b/.golangci.yaml index c9c9f620b..2e0ed3c7b 100644 --- a/.golangci.yaml +++ b/.golangci.yaml @@ -32,6 +32,10 @@ linters: linters-settings: gci: sections: [standard, default, localmodule] + staticcheck: + checks: + - all + - -SA1019 # omit Deprecated check severity: default-severity: error rules: diff --git a/api/types.go b/api/types.go index 2f5a94241..df7bab210 100644 --- a/api/types.go +++ b/api/types.go @@ -296,15 +296,17 @@ type EmbeddingResponse struct { // CreateRequest is the request passed to [Client.Create]. type CreateRequest struct { Model string `json:"model"` - Path string `json:"path"` Modelfile string `json:"modelfile"` Stream *bool `json:"stream,omitempty"` Quantize string `json:"quantize,omitempty"` - // Name is deprecated, see Model + // Deprecated: set the model name with Model instead Name string `json:"name"` - // Quantization is deprecated, see Quantize + // Deprecated: set the file content with Modelfile instead + Path string `json:"path"` + + // Deprecated: use Quantize instead Quantization string `json:"quantization,omitempty"` } @@ -312,7 +314,7 @@ type CreateRequest struct { type DeleteRequest struct { Model string `json:"model"` - // Name is deprecated, see Model + // Deprecated: set the model name with Model instead Name string `json:"name"` } @@ -327,7 +329,7 @@ type ShowRequest struct { Options map[string]interface{} `json:"options"` - // Name is deprecated, see Model + // Deprecated: set the model name with Model instead Name string `json:"name"` } @@ -359,7 +361,7 @@ type PullRequest struct { Password string `json:"password"` Stream *bool `json:"stream,omitempty"` - // Name is deprecated, see Model + // Deprecated: set the model name with Model instead Name string `json:"name"` } @@ -380,7 +382,7 @@ type PushRequest struct { Password string `json:"password"` Stream *bool `json:"stream,omitempty"` - // Name is deprecated, see Model + // Deprecated: set the model name with Model instead Name string `json:"name"` } From 7416ced70f02f18f353d4025b817148d04867a3f Mon Sep 17 00:00:00 2001 From: Patrick Devine Date: Wed, 28 Aug 2024 14:03:20 -0700 Subject: [PATCH 298/384] add llama3.1 chat template (#6545) --- template/index.json | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/template/index.json b/template/index.json index e2d418932..0ce6ac0f2 100644 --- a/template/index.json +++ b/template/index.json @@ -91,6 +91,10 @@ "template": "{% set loop_messages = messages %}{% for message in loop_messages %}{% set content = '<|start_header_id|>' + message['role'] + '<|end_header_id|>\n\n'+ message['content'] | trim + '<|eot_id|>' %}{% if loop.index0 == 0 %}{% set content = bos_token + content %}{% endif %}{{ content }}{% endfor %}{% if add_generation_prompt %}{{ '<|start_header_id|>assistant<|end_header_id|>\n\n' }}{% endif %}", "name": "llama3-instruct" }, + { + "template": "{{- bos_token }}\n{%- if custom_tools is defined %}\n {%- set tools = custom_tools %}\n{%- endif %}\n{%- if not tools_in_user_message is defined %}\n {%- set tools_in_user_message = true %}\n{%- endif %}\n{%- if not date_string is defined %}\n {%- set date_string = \"26 Jul 2024\" %}\n{%- endif %}\n{%- if not tools is defined %}\n {%- set tools = none %}\n{%- endif %}\n\n{#- This block extracts the system message, so we can slot it into the right place. #}\n{%- if messages[0]['role'] == 'system' %}\n {%- set system_message = messages[0]['content']|trim %}\n {%- set messages = messages[1:] %}\n{%- else %}\n {%- set system_message = \"\" %}\n{%- endif %}\n\n{#- System message + builtin tools #}\n{{- \"<|start_header_id|>system<|end_header_id|>\\n\\n\" }}\n{%- if builtin_tools is defined or tools is not none %}\n {{- \"Environment: ipython\\n\" }}\n{%- endif %}\n{%- if builtin_tools is defined %}\n {{- \"Tools: \" + builtin_tools | reject('equalto', 'code_interpreter') | join(\", \") + \"\\n\\n\"}}\n{%- endif %}\n{{- \"Cutting Knowledge Date: December 2023\\n\" }}\n{{- \"Today Date: \" + date_string + \"\\n\\n\" }}\n{%- if tools is not none and not tools_in_user_message %}\n {{- \"You have access to the following functions. To call a function, please respond with JSON for a function call.\" }}\n {{- 'Respond in the format {\"name\": function name, \"parameters\": dictionary of argument name and its value}.' }}\n {{- \"Do not use variables.\\n\\n\" }}\n {%- for t in tools %}\n {{- t | tojson(indent=4) }}\n {{- \"\\n\\n\" }}\n {%- endfor %}\n{%- endif %}\n{{- system_message }}\n{{- \"<|eot_id|>\" }}\n\n{#- Custom tools are passed in a user message with some extra guidance #}\n{%- if tools_in_user_message and not tools is none %}\n {#- Extract the first user message so we can plug it in here #}\n {%- if messages | length != 0 %}\n {%- set first_user_message = messages[0]['content']|trim %}\n {%- set messages = messages[1:] %}\n {%- else %}\n {{- raise_exception(\"Cannot put tools in the first user message when there's no first user message!\") }}\n{%- endif %}\n {{- '<|start_header_id|>user<|end_header_id|>\\n\\n' -}}\n {{- \"Given the following functions, please respond with a JSON for a function call \" }}\n {{- \"with its proper arguments that best answers the given prompt.\\n\\n\" }}\n {{- 'Respond in the format {\"name\": function name, \"parameters\": dictionary of argument name and its value}.' }}\n {{- \"Do not use variables.\\n\\n\" }}\n {%- for t in tools %}\n {{- t | tojson(indent=4) }}\n {{- \"\\n\\n\" }}\n {%- endfor %}\n {{- first_user_message + \"<|eot_id|>\"}}\n{%- endif %}\n\n{%- for message in messages %}\n {%- if not (message.role == 'ipython' or message.role == 'tool' or 'tool_calls' in message) %}\n {{- '<|start_header_id|>' + message['role'] + '<|end_header_id|>\\n\\n'+ message['content'] | trim + '<|eot_id|>' }}\n {%- elif 'tool_calls' in message %}\n {%- if not message.tool_calls|length == 1 %}\n {{- raise_exception(\"This model only supports single tool-calls at once!\") }}\n {%- endif %}\n {%- set tool_call = message.tool_calls[0].function %}\n {%- if builtin_tools is defined and tool_call.name in builtin_tools %}\n {{- '<|start_header_id|>assistant<|end_header_id|>\\n\\n' -}}\n {{- \"<|python_tag|>\" + tool_call.name + \".call(\" }}\n {%- for arg_name, arg_val in tool_call.arguments | items %}\n {{- arg_name + '=\"' + arg_val + '\"' }}\n {%- if not loop.last %}\n {{- \", \" }}\n {%- endif %}\n {%- endfor %}\n {{- \")\" }}\n {%- else %}\n {{- '<|start_header_id|>assistant<|end_header_id|>\\n\\n' -}}\n {{- '{\"name\": \"' + tool_call.name + '\", ' }}\n {{- '\"parameters\": ' }}\n {{- tool_call.arguments | tojson }}\n {{- \"}\" }}\n {%- endif %}\n {%- if builtin_tools is defined %}\n {#- This means we're in ipython mode #}\n {{- \"<|eom_id|>\" }}\n {%- else %}\n {{- \"<|eot_id|>\" }}\n {%- endif %}\n {%- elif message.role == \"tool\" or message.role == \"ipython\" %}\n {{- \"<|start_header_id|>ipython<|end_header_id|>\\n\\n\" }}\n {%- if message.content is mapping or message.content is iterable %}\n {{- message.content | tojson }}\n {%- else %}\n {{- message.content }}\n {%- endif %}\n {{- \"<|eot_id|>\" }}\n {%- endif %}\n{%- endfor %}\n{%- if add_generation_prompt %}\n {{- '<|start_header_id|>assistant<|end_header_id|>\\n\\n' }}\n{%- endif %}\n", + "name": "llama3-instruct" + }, { "template": "{% for message in messages %}\n{% if message['role'] == 'user' %}\n{{ 'Question:\n' + message['content'] + '\n\n' }}{% elif message['role'] == 'system' %}\n{{ 'System:\n' + message['content'] + '\n\n' }}{% elif message['role'] == 'assistant' %}{{ 'Answer:\n' + message['content'] + '\n\n' }}{% endif %}\n{% if loop.last and add_generation_prompt %}\n{{ 'Answer:\n' }}{% endif %}{% endfor %}", "name": "granite-instruct" From e4d0a9c325137b684d9c0bb02694e8f10197314a Mon Sep 17 00:00:00 2001 From: Michael Yang Date: Wed, 28 Aug 2024 14:07:48 -0700 Subject: [PATCH 299/384] fix(test): do not clobber models directory --- server/model_test.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/server/model_test.go b/server/model_test.go index 7753c5498..e1737a5b5 100644 --- a/server/model_test.go +++ b/server/model_test.go @@ -139,6 +139,7 @@ The temperature in San Francisco, CA is 70°F and in Toronto, Canada is 20°C.`, func TestParseFromFileFromLayer(t *testing.T) { tempModels := t.TempDir() + t.Setenv("OLLAMA_MODELS", tempModels) file, err := os.CreateTemp(tempModels, "") if err != nil { @@ -189,6 +190,7 @@ func TestParseFromFileFromLayer(t *testing.T) { func TestParseLayerFromCopy(t *testing.T) { tempModels := t.TempDir() + t.Setenv("OLLAMA_MODELS", tempModels) file2, err := os.CreateTemp(tempModels, "") if err != nil { From 8e4e509fa4e8e1c49cedfc2754e9a0c9ed0f2fae Mon Sep 17 00:00:00 2001 From: Patrick Devine Date: Wed, 28 Aug 2024 17:11:46 -0700 Subject: [PATCH 300/384] update the openai docs to explain how to set the context size (#6548) --- docs/openai.md | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/docs/openai.md b/docs/openai.md index 75d2c5955..0cbea6cc9 100644 --- a/docs/openai.md +++ b/docs/openai.md @@ -300,3 +300,28 @@ curl http://localhost:11434/v1/chat/completions \ ] }' ``` + +### Setting the context size + +The OpenAI API does not have a way of setting the context size for a model. If you need to change the context size, create a `Modelfile` which looks like: + +```modelfile +FROM +PARAMETER num_ctx +``` + +Use the `ollama create mymodel` command to create a new model with the updated context size. Call the API with the updated model name: + +```shell +curl http://localhost:11434/v1/chat/completions \ + -H "Content-Type: application/json" \ + -d '{ + "model": "mymodel", + "messages": [ + { + "role": "user", + "content": "Hello!" + } + ] + }' +``` From 56346ccfa3e51eec51fc26ae8e91fc88cb74a9b8 Mon Sep 17 00:00:00 2001 From: Bryan Honof Date: Thu, 29 Aug 2024 18:45:35 +0200 Subject: [PATCH 301/384] doc: Add Nix and Flox to package manager listing (#6074) --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index aae92e6c2..9f643800a 100644 --- a/README.md +++ b/README.md @@ -337,6 +337,8 @@ See the [API documentation](./docs/api.md) for all endpoints. - [Pacman](https://archlinux.org/packages/extra/x86_64/ollama/) - [Helm Chart](https://artifacthub.io/packages/helm/ollama-helm/ollama) - [Guix channel](https://codeberg.org/tusharhero/ollama-guix) +- [Nix package](https://search.nixos.org/packages?channel=24.05&show=ollama&from=0&size=50&sort=relevance&type=packages&query=ollama) +- [Flox](https://flox.dev/blog/ollama-part-one) ### Libraries From 11018196e0e15b78328f710cef707c09eabcbd8d Mon Sep 17 00:00:00 2001 From: Michael Yang Date: Thu, 29 Aug 2024 13:40:43 -0700 Subject: [PATCH 302/384] remove any unneeded build artifacts --- llm/generate/gen_common.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/llm/generate/gen_common.sh b/llm/generate/gen_common.sh index 40115936d..cef68ea16 100644 --- a/llm/generate/gen_common.sh +++ b/llm/generate/gen_common.sh @@ -87,6 +87,8 @@ apply_patches() { build() { cmake -S ${LLAMACPP_DIR} -B ${BUILD_DIR} ${CMAKE_DEFS} cmake --build ${BUILD_DIR} ${CMAKE_TARGETS} -j8 + # remove unnecessary build artifacts + rm -f ${BUILD_DIR}/bin/ggml-common.h ${BUILD_DIR}/bin/ggml-metal.metal } compress() { From a1cef4d0a5f31280ea82b350605775931a6163cb Mon Sep 17 00:00:00 2001 From: Daniel Hiltgen Date: Sat, 31 Aug 2024 10:40:05 -0700 Subject: [PATCH 303/384] Add findutils to base images (#6581) This caused missing internal files --- scripts/rh_linux_deps.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scripts/rh_linux_deps.sh b/scripts/rh_linux_deps.sh index b4c9afd63..23f1f650e 100644 --- a/scripts/rh_linux_deps.sh +++ b/scripts/rh_linux_deps.sh @@ -30,7 +30,7 @@ if grep -i "centos" /etc/system-release >/dev/null; then dnf install -y rh-git227-git ln -s /opt/rh/rh-git227/root/usr/bin/git /usr/local/bin/git fi - dnf install -y devtoolset-10-gcc devtoolset-10-gcc-c++ pigz + dnf install -y devtoolset-10-gcc devtoolset-10-gcc-c++ pigz findutils elif grep -i "rocky" /etc/system-release >/dev/null; then # Temporary workaround until rocky 8 AppStream ships GCC 10.4 (10.3 is incompatible with NVCC) cat << EOF > /etc/yum.repos.d/Rocky-Vault.repo @@ -45,6 +45,7 @@ EOF dnf install -y git \ gcc-toolset-10-gcc-10.2.1-8.2.el8 \ gcc-toolset-10-gcc-c++-10.2.1-8.2.el8 \ + findutils \ pigz else echo "ERROR Unexpected distro" From 1aad838707227eaf1be92ff6dd2fd3a6662858c2 Mon Sep 17 00:00:00 2001 From: rayfiyo <108730891+rayfiyo@users.noreply.github.com> Date: Sun, 1 Sep 2024 11:34:25 +0900 Subject: [PATCH 304/384] docs: update GGUF examples and references (#6577) --- docs/modelfile.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/modelfile.md b/docs/modelfile.md index 51827e749..92df22ef5 100644 --- a/docs/modelfile.md +++ b/docs/modelfile.md @@ -128,10 +128,10 @@ Currently supported model architectures: #### Build from a GGUF file ```modelfile -FROM ./ollama-model.bin +FROM ./ollama-model.gguf ``` -The GGUF bin file location should be specified as an absolute path or relative to the `Modelfile` location. +The GGUF file location should be specified as an absolute path or relative to the `Modelfile` location. ### PARAMETER @@ -208,7 +208,7 @@ Currently supported Safetensor adapters: #### GGUF adapter ```modelfile -ADAPTER ./ollama-lora.bin +ADAPTER ./ollama-lora.gguf ``` ### LICENSE From 5f7b4a5e3056d083997b744029c30614cd32397b Mon Sep 17 00:00:00 2001 From: Vimal Kumar Date: Sun, 1 Sep 2024 09:42:17 +0530 Subject: [PATCH 305/384] fix(cmd): show info may have nil ModelInfo (#6579) --- cmd/cmd.go | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/cmd/cmd.go b/cmd/cmd.go index b75c0b5ec..f6d31f5bb 100644 --- a/cmd/cmd.go +++ b/cmd/cmd.go @@ -726,14 +726,17 @@ func ShowHandler(cmd *cobra.Command, args []string) error { } func showInfo(resp *api.ShowResponse) { - arch := resp.ModelInfo["general.architecture"].(string) - modelData := [][]string{ - {"arch", arch}, {"parameters", resp.Details.ParameterSize}, {"quantization", resp.Details.QuantizationLevel}, - {"context length", fmt.Sprintf("%v", resp.ModelInfo[fmt.Sprintf("%s.context_length", arch)].(float64))}, - {"embedding length", fmt.Sprintf("%v", resp.ModelInfo[fmt.Sprintf("%s.embedding_length", arch)].(float64))}, + } + if resp.ModelInfo != nil { + arch := resp.ModelInfo["general.architecture"].(string) + modelData = append(modelData, + []string{"arch", arch}, + []string{"context length", fmt.Sprintf("%v", resp.ModelInfo[fmt.Sprintf("%s.context_length", arch)].(float64))}, + []string{"embedding length", fmt.Sprintf("%v", resp.ModelInfo[fmt.Sprintf("%s.embedding_length", arch)].(float64))}, + ) } mainTableData := [][]string{ From 741affdfd6938bebcec34a2ee4c40fe2584a63bc Mon Sep 17 00:00:00 2001 From: SnoopyTlion <771933002@qq.com> Date: Tue, 3 Sep 2024 03:31:29 +0800 Subject: [PATCH 306/384] docs: update faq.md for OLLAMA_MODELS env var permissions (#6587) --- docs/faq.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/faq.md b/docs/faq.md index 25b682483..356d51057 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -194,6 +194,8 @@ Refer to the section [above](#how-do-i-configure-ollama-server) for how to set e If a different directory needs to be used, set the environment variable `OLLAMA_MODELS` to the chosen directory. +> Note: on Linux using the standard installer, the `ollama` user needs read and write access to the specified directory. To assign the directory to the `ollama` user run `sudo chown -R ollama:ollama `. + Refer to the section [above](#how-do-i-configure-ollama-server) for how to set environment variables on your platform. ## How can I use Ollama in Visual Studio Code? From bfc2d61549534687277256590dce16864dda40c7 Mon Sep 17 00:00:00 2001 From: Jonathan Hecl Date: Mon, 2 Sep 2024 16:34:26 -0300 Subject: [PATCH 307/384] readme: add go-crew and Ollamaclient projects (#6583) --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 9f643800a..1767d2dc9 100644 --- a/README.md +++ b/README.md @@ -302,6 +302,7 @@ See the [API documentation](./docs/api.md) for all endpoints. - [LLMStack](https://github.com/trypromptly/LLMStack) (No-code multi-agent framework to build LLM agents and workflows) - [BoltAI for Mac](https://boltai.com) (AI Chat Client for Mac) - [Harbor](https://github.com/av/harbor) (Containerized LLM Toolkit with Ollama as default backend) +- [Go-CREW](https://www.jonathanhecl.com/go-crew/) (Powerful Offline RAG in Golang) ### Terminal @@ -370,6 +371,7 @@ See the [API documentation](./docs/api.md) for all endpoints. - [Portkey](https://portkey.ai/docs/welcome/integration-guides/ollama) - [PromptingTools.jl](https://github.com/svilupp/PromptingTools.jl) with an [example](https://svilupp.github.io/PromptingTools.jl/dev/examples/working_with_ollama) - [LlamaScript](https://github.com/Project-Llama/llamascript) +- [Ollamaclient for Golang](https://github.com/xyproto/ollamaclient) ### Mobile From ad3eb00bee71052576807b8cf4b9a25b2cceba50 Mon Sep 17 00:00:00 2001 From: presbrey Date: Mon, 2 Sep 2024 16:05:36 -0400 Subject: [PATCH 308/384] readme: add OllamaFarm project (#6508) --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 1767d2dc9..84368f8d6 100644 --- a/README.md +++ b/README.md @@ -350,6 +350,7 @@ See the [API documentation](./docs/api.md) for all endpoints. - [LangChainRust](https://github.com/Abraxas-365/langchain-rust) with [example](https://github.com/Abraxas-365/langchain-rust/blob/main/examples/llm_ollama.rs) - [LlamaIndex](https://gpt-index.readthedocs.io/en/stable/examples/llm/ollama.html) - [LiteLLM](https://github.com/BerriAI/litellm) +- [OllamaFarm for Go](https://github.com/presbrey/ollamafarm) - [OllamaSharp for .NET](https://github.com/awaescher/OllamaSharp) - [Ollama for Ruby](https://github.com/gbaptista/ollama-ai) - [Ollama-rs for Rust](https://github.com/pepperoni21/ollama-rs) From 9df5f0e8e4d9a7f2f0fdfc782304df8dbcbd50c6 Mon Sep 17 00:00:00 2001 From: R0CKSTAR Date: Wed, 4 Sep 2024 00:25:31 +0800 Subject: [PATCH 309/384] Reduce docker image size (#5847) Signed-off-by: Xiaodong Ye --- Dockerfile | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/Dockerfile b/Dockerfile index c46477b49..6743866a4 100644 --- a/Dockerfile +++ b/Dockerfile @@ -21,7 +21,7 @@ COPY --from=llm-code / /go/src/github.com/ollama/ollama/ WORKDIR /go/src/github.com/ollama/ollama/llm/generate ARG CGO_CFLAGS ARG CUDA_V11_ARCHITECTURES -ENV GOARCH amd64 +ENV GOARCH amd64 RUN --mount=type=cache,target=/root/.ccache \ OLLAMA_SKIP_STATIC_GENERATE=1 \ OLLAMA_SKIP_CPU_GENERATE=1 \ @@ -38,7 +38,7 @@ COPY --from=llm-code / /go/src/github.com/ollama/ollama/ WORKDIR /go/src/github.com/ollama/ollama/llm/generate ARG CGO_CFLAGS ARG CUDA_V12_ARCHITECTURES -ENV GOARCH amd64 +ENV GOARCH amd64 RUN --mount=type=cache,target=/root/.ccache \ OLLAMA_SKIP_STATIC_GENERATE=1 \ OLLAMA_SKIP_CPU_GENERATE=1 \ @@ -56,7 +56,7 @@ COPY --from=llm-code / /go/src/github.com/ollama/ollama/ WORKDIR /go/src/github.com/ollama/ollama/llm/generate ARG CGO_CFLAGS ARG CUDA_V11_ARCHITECTURES -ENV GOARCH arm64 +ENV GOARCH arm64 RUN OLLAMA_SKIP_STATIC_GENERATE=1 \ OLLAMA_SKIP_CPU_GENERATE=1 \ CMAKE_CUDA_ARCHITECTURES="${CUDA_V11_ARCHITECTURES}" \ @@ -72,7 +72,7 @@ COPY --from=llm-code / /go/src/github.com/ollama/ollama/ WORKDIR /go/src/github.com/ollama/ollama/llm/generate ARG CGO_CFLAGS ARG CUDA_V12_ARCHITECTURES -ENV GOARCH arm64 +ENV GOARCH arm64 RUN --mount=type=cache,target=/root/.ccache \ OLLAMA_SKIP_STATIC_GENERATE=1 \ OLLAMA_SKIP_CPU_GENERATE=1 \ @@ -92,7 +92,7 @@ COPY --from=llm-code / /go/src/github.com/ollama/ollama/ WORKDIR /go/src/github.com/ollama/ollama/llm/generate ARG CGO_CFLAGS ARG AMDGPU_TARGETS -ENV GOARCH amd64 +ENV GOARCH amd64 RUN --mount=type=cache,target=/root/.ccache \ OLLAMA_SKIP_STATIC_GENERATE=1 OLLAMA_SKIP_CPU_GENERATE=1 bash gen_linux.sh RUN mkdir -p ../../dist/linux-amd64-rocm/lib/ollama && \ @@ -107,7 +107,7 @@ ENV PATH /opt/rh/devtoolset-10/root/usr/bin:$PATH COPY --from=llm-code / /go/src/github.com/ollama/ollama/ ARG OLLAMA_CUSTOM_CPU_DEFS ARG CGO_CFLAGS -ENV GOARCH amd64 +ENV GOARCH amd64 WORKDIR /go/src/github.com/ollama/ollama/llm/generate FROM --platform=linux/amd64 cpu-builder-amd64 AS static-build-amd64 @@ -181,17 +181,19 @@ RUN --mount=type=cache,target=/root/.ccache \ # Strip out ROCm dependencies to keep the primary image lean FROM --platform=linux/amd64 ubuntu:22.04 as amd64-libs-without-rocm COPY --from=build-amd64 /go/src/github.com/ollama/ollama/dist/linux-amd64/lib/ /scratch/ -RUN cd /scratch/ollama/ && rm -rf rocblas libamd* libdrm* libroc* libhip* libhsa* +RUN cd /scratch/ollama/ && rm -rf rocblas libamd* libdrm* libroc* libhip* libhsa* # Runtime stages FROM --platform=linux/amd64 ubuntu:22.04 as runtime-amd64 COPY --from=amd64-libs-without-rocm /scratch/ /lib/ -RUN apt-get update && apt-get install -y ca-certificates +RUN apt-get update && apt-get install -y ca-certificates && \ + apt-get clean && rm -rf /var/lib/apt/lists/* COPY --from=build-amd64 /go/src/github.com/ollama/ollama/dist/linux-amd64/bin/ /bin/ FROM --platform=linux/arm64 ubuntu:22.04 as runtime-arm64 COPY --from=build-arm64 /go/src/github.com/ollama/ollama/dist/linux-arm64/lib/ /lib/ -RUN apt-get update && apt-get install -y ca-certificates +RUN apt-get update && apt-get install -y ca-certificates && \ + apt-get clean && rm -rf /var/lib/apt/lists/* COPY --from=build-arm64 /go/src/github.com/ollama/ollama/dist/linux-arm64/bin/ /bin/ # Radeon images are much larger so we keep it distinct from the CPU/CUDA image From 14d5093cd02373f11ed138241e3e2eedf349824b Mon Sep 17 00:00:00 2001 From: OpenVMP Date: Tue, 3 Sep 2024 09:28:01 -0700 Subject: [PATCH 310/384] readme: add PartCAD tool to readme for generating 3D CAD models using Ollama (#6605) --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 84368f8d6..3b619ae0c 100644 --- a/README.md +++ b/README.md @@ -303,6 +303,7 @@ See the [API documentation](./docs/api.md) for all endpoints. - [BoltAI for Mac](https://boltai.com) (AI Chat Client for Mac) - [Harbor](https://github.com/av/harbor) (Containerized LLM Toolkit with Ollama as default backend) - [Go-CREW](https://www.jonathanhecl.com/go-crew/) (Powerful Offline RAG in Golang) +- [PartCAD](https://github.com/openvmp/partcad/) (CAD model generation with OpenSCAD and CadQuery) ### Terminal From 94fff5805fecd25287e1a21ac9910859011adcff Mon Sep 17 00:00:00 2001 From: FellowTraveler Date: Tue, 3 Sep 2024 11:32:59 -0500 Subject: [PATCH 311/384] Fix sprintf to snprintf (#5664) /Users/au/src/ollama/llm/ext_server/server.cpp:289:9: warning: 'sprintf' is deprecated: This function is provided for compatibility reasons only. Due to security concerns inherent in the design of sprintf(3), it is highly recommended that you use snprintf(3) instead. --- llm/ext_server/server.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/llm/ext_server/server.cpp b/llm/ext_server/server.cpp index 8e08b850f..1e2306111 100644 --- a/llm/ext_server/server.cpp +++ b/llm/ext_server/server.cpp @@ -262,7 +262,7 @@ struct server_slot { char buffer[512]; double t_token = t_prompt_processing / n_prompt_tokens_processed; double n_tokens_second = 1e3 / t_prompt_processing * n_prompt_tokens_processed; - sprintf(buffer, "prompt eval time = %10.2f ms / %5d tokens (%8.2f ms per token, %8.2f tokens per second)", + snprintf(buffer, sizeof(buffer), "prompt eval time = %10.2f ms / %5d tokens (%8.2f ms per token, %8.2f tokens per second)", t_prompt_processing, n_prompt_tokens_processed, t_token, n_tokens_second); LOG_DEBUG(buffer, { @@ -276,7 +276,7 @@ struct server_slot { t_token = t_token_generation / n_decoded; n_tokens_second = 1e3 / t_token_generation * n_decoded; - sprintf(buffer, "generation eval time = %10.2f ms / %5d runs (%8.2f ms per token, %8.2f tokens per second)", + snprintf(buffer, sizeof(buffer), "generation eval time = %10.2f ms / %5d runs (%8.2f ms per token, %8.2f tokens per second)", t_token_generation, n_decoded, t_token, n_tokens_second); LOG_DEBUG(buffer, { @@ -288,7 +288,7 @@ struct server_slot { {"n_tokens_second", n_tokens_second}, }); - sprintf(buffer, " total time = %10.2f ms", t_prompt_processing + t_token_generation); + snprintf(buffer, sizeof(buffer), " total time = %10.2f ms", t_prompt_processing + t_token_generation); LOG_DEBUG(buffer, { {"slot_id", id}, {"task_id", task_id}, From 35159de18a6054f30d6eb73747f150c519cbf0b9 Mon Sep 17 00:00:00 2001 From: Amith Koujalgi Date: Wed, 4 Sep 2024 01:38:50 +0530 Subject: [PATCH 312/384] readme: update Ollama4j link and add link to Ollama4j Web UI (#6608) --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 3b619ae0c..5568b6e7d 100644 --- a/README.md +++ b/README.md @@ -304,6 +304,7 @@ See the [API documentation](./docs/api.md) for all endpoints. - [Harbor](https://github.com/av/harbor) (Containerized LLM Toolkit with Ollama as default backend) - [Go-CREW](https://www.jonathanhecl.com/go-crew/) (Powerful Offline RAG in Golang) - [PartCAD](https://github.com/openvmp/partcad/) (CAD model generation with OpenSCAD and CadQuery) +- [Ollama4j Web UI](https://github.com/ollama4j/ollama4j-web-ui) - Java-based Web UI for Ollama built with Vaadin, Spring Boot and Ollama4j ### Terminal @@ -356,7 +357,7 @@ See the [API documentation](./docs/api.md) for all endpoints. - [Ollama for Ruby](https://github.com/gbaptista/ollama-ai) - [Ollama-rs for Rust](https://github.com/pepperoni21/ollama-rs) - [Ollama-hpp for C++](https://github.com/jmont-dev/ollama-hpp) -- [Ollama4j for Java](https://github.com/amithkoujalgi/ollama4j) +- [Ollama4j for Java](https://github.com/ollama4j/ollama4j) - [ModelFusion Typescript Library](https://modelfusion.dev/integration/model-provider/ollama) - [OllamaKit for Swift](https://github.com/kevinhermawan/OllamaKit) - [Ollama for Dart](https://github.com/breitburg/dart-ollama) From 50c05d57e0100625ee5bdd4ba9accec7f4536005 Mon Sep 17 00:00:00 2001 From: Mateusz Migas <54471371+mateuszmigas@users.noreply.github.com> Date: Tue, 3 Sep 2024 22:15:54 +0200 Subject: [PATCH 313/384] readme: add Painting Droid community integration (#5514) --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 5568b6e7d..fee01baa0 100644 --- a/README.md +++ b/README.md @@ -296,6 +296,7 @@ See the [API documentation](./docs/api.md) for all endpoints. - [OllamaSpring](https://github.com/CrazyNeil/OllamaSpring) (Ollama Client for macOS) - [LLocal.in](https://github.com/kartikm7/llocal) (Easy to use Electron Desktop Client for Ollama) - [Ollama with Google Mesop](https://github.com/rapidarchitect/ollama_mesop/) (Mesop Chat Client implementation with Ollama) +- [Painting Droid](https://github.com/mateuszmigas/painting-droid) (Painting app with AI integrations) - [Kerlig AI](https://www.kerlig.com/) (AI writing assistant for macOS) - [AI Studio](https://github.com/MindWorkAI/AI-Studio) - [Sidellama](https://github.com/gyopak/sidellama) (browser-based LLM client) From 037a4d103edff143db2f82e1feb8b5b80afea6f1 Mon Sep 17 00:00:00 2001 From: Daniel Hiltgen Date: Tue, 3 Sep 2024 14:55:20 -0700 Subject: [PATCH 314/384] Log system memory at info (#6617) On systems with low system memory, we can hit allocation failures that are difficult to diagnose without debug logs. This will make it easier to spot. --- llm/server.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/llm/server.go b/llm/server.go index c38bc6bb8..9c08f1bb4 100644 --- a/llm/server.go +++ b/llm/server.go @@ -98,7 +98,7 @@ func NewLlamaServer(gpus gpu.GpuInfoList, model string, ggml *GGML, adapters, pr systemTotalMemory = systemMemInfo.TotalMemory systemFreeMemory = systemMemInfo.FreeMemory systemSwapFreeMemory = systemMemInfo.FreeSwap - slog.Debug("system memory", "total", format.HumanBytes2(systemTotalMemory), "free", format.HumanBytes2(systemFreeMemory), "free_swap", format.HumanBytes2(systemSwapFreeMemory)) + slog.Info("system memory", "total", format.HumanBytes2(systemTotalMemory), "free", format.HumanBytes2(systemFreeMemory), "free_swap", format.HumanBytes2(systemSwapFreeMemory)) } // If the user wants zero GPU layers, reset the gpu list to be CPU/system ram info From f29b167e1af8a7c0e8a15044584826432aff76d8 Mon Sep 17 00:00:00 2001 From: Daniel Hiltgen Date: Tue, 3 Sep 2024 17:15:31 -0700 Subject: [PATCH 315/384] Use cuda v11 for driver 525 and older (#6620) It looks like driver 525 (aka, cuda driver 12.0) has problems with the cuda v12 library we compile against, so run v11 on those older drivers if detected. --- gpu/cuda_common.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gpu/cuda_common.go b/gpu/cuda_common.go index 827cc9b48..aceec70af 100644 --- a/gpu/cuda_common.go +++ b/gpu/cuda_common.go @@ -57,7 +57,7 @@ func cudaVariant(gpuInfo CudaGPUInfo) string { } } - if gpuInfo.computeMajor < 6 || gpuInfo.DriverMajor < 12 { + if gpuInfo.computeMajor < 6 || gpuInfo.DriverMajor < 12 || (gpuInfo.DriverMajor == 12 && gpuInfo.DriverMinor == 0) { return "v11" } return "v12" From 5e2653f9fe454e948a8d48e3c15c21830c1ac26b Mon Sep 17 00:00:00 2001 From: Jeffrey Morgan Date: Tue, 3 Sep 2024 21:12:39 -0400 Subject: [PATCH 316/384] llm: update llama.cpp commit to 8962422 (#6618) --- llm/ext_server/server.cpp | 19 +- llm/generate/gen_darwin.sh | 2 +- llm/llama.cpp | 2 +- llm/patches/05-default-pretokenizer.diff | 10 +- llm/patches/06-embeddings.diff | 28 +- llm/patches/09-lora.diff | 350 ----------------------- llm/patches/11-phi3-sliding-window.diff | 43 --- 7 files changed, 32 insertions(+), 422 deletions(-) delete mode 100644 llm/patches/09-lora.diff delete mode 100644 llm/patches/11-phi3-sliding-window.diff diff --git a/llm/ext_server/server.cpp b/llm/ext_server/server.cpp index 1e2306111..fc673c474 100644 --- a/llm/ext_server/server.cpp +++ b/llm/ext_server/server.cpp @@ -425,7 +425,7 @@ struct llama_server_context n_ctx = llama_n_ctx(ctx); - add_bos_token = llama_should_add_bos_token(model); + add_bos_token = llama_add_bos_token(model); return true; } @@ -1031,7 +1031,7 @@ struct llama_server_context continue; } - if (!llava_image_embed_make_with_clip_img(clp_ctx, params.n_threads, img.img_data, &img.image_embedding, &img.image_tokens)) { + if (!llava_image_embed_make_with_clip_img(clp_ctx, params.cpuparams.n_threads, img.img_data, &img.image_embedding, &img.image_tokens)) { LOG_TEE("Error processing the given image"); return false; } @@ -2014,7 +2014,7 @@ static void server_print_usage(const char *argv0, const gpt_params ¶ms, printf("options:\n"); printf(" -h, --help show this help message and exit\n"); printf(" -v, --verbose verbose output (default: %s)\n", server_verbose ? "enabled" : "disabled"); - printf(" -t N, --threads N number of threads to use during computation (default: %d)\n", params.n_threads); + printf(" -t N, --threads N number of threads to use during computation (default: %d)\n", params.cpuparams.n_threads); printf(" -tb N, --threads-batch N number of threads to use during batch and prompt processing (default: same as --threads)\n"); printf(" --threads-http N number of threads in the http server pool to process requests (default: max(hardware concurrency - 1, --parallel N + 2))\n"); printf(" -c N, --ctx-size N size of the prompt context (default: %d)\n", params.n_ctx); @@ -2287,7 +2287,7 @@ static void server_params_parse(int argc, char **argv, server_params &sparams, g invalid_param = true; break; } - params.n_threads = std::stoi(argv[i]); + params.cpuparams.n_threads = std::stoi(argv[i]); } else if (arg == "--grp-attn-n" || arg == "-gan") { @@ -2315,7 +2315,7 @@ static void server_params_parse(int argc, char **argv, server_params &sparams, g invalid_param = true; break; } - params.n_threads_batch = std::stoi(argv[i]); + params.cpuparams_batch.n_threads = std::stoi(argv[i]); } else if (arg == "--threads-http") { @@ -2626,6 +2626,11 @@ static void server_params_parse(int argc, char **argv, server_params &sparams, g params.kv_overrides.back().key[0] = 0; } + postprocess_cpu_params(params.cpuparams, nullptr); + postprocess_cpu_params(params.cpuparams_batch, ¶ms.cpuparams); + postprocess_cpu_params(params.draft_cpuparams, ¶ms.cpuparams); + postprocess_cpu_params(params.draft_cpuparams_batch, ¶ms.cpuparams_batch); + if (invalid_param) { fprintf(stderr, "error: invalid parameter for argument: %s\n", arg.c_str()); @@ -2775,8 +2780,8 @@ int main(int argc, char **argv) { {"commit", LLAMA_COMMIT}}); LOG_INFO("system info", { - {"n_threads", params.n_threads}, - {"n_threads_batch", params.n_threads_batch}, + {"n_threads", params.cpuparams.n_threads}, + {"n_threads_batch", params.cpuparams_batch.n_threads}, {"total_threads", std::thread::hardware_concurrency()}, {"system_info", llama_print_system_info()}, }); diff --git a/llm/generate/gen_darwin.sh b/llm/generate/gen_darwin.sh index f22c0f8e7..acea9c8da 100755 --- a/llm/generate/gen_darwin.sh +++ b/llm/generate/gen_darwin.sh @@ -19,7 +19,7 @@ sign() { fi } -COMMON_DARWIN_DEFS="-DBUILD_SHARED_LIBS=off -DCMAKE_OSX_DEPLOYMENT_TARGET=11.3 -DLLAMA_METAL_MACOSX_VERSION_MIN=11.3 -DCMAKE_SYSTEM_NAME=Darwin -DGGML_METAL_EMBED_LIBRARY=on -DGGML_OPENMP=off" +COMMON_DARWIN_DEFS="-DBUILD_SHARED_LIBS=off -DCMAKE_OSX_DEPLOYMENT_TARGET=11.3 -DGGML_METAL_MACOSX_VERSION_MIN=11.3 -DCMAKE_SYSTEM_NAME=Darwin -DGGML_METAL_EMBED_LIBRARY=on -DGGML_OPENMP=off" case "${GOARCH}" in "amd64") diff --git a/llm/llama.cpp b/llm/llama.cpp index 1e6f6554a..8962422b1 160000 --- a/llm/llama.cpp +++ b/llm/llama.cpp @@ -1 +1 @@ -Subproject commit 1e6f6554aa11fa10160a5fda689e736c3c34169f +Subproject commit 8962422b1c6f9b8b15f5aeaea42600bcc2d44177 diff --git a/llm/patches/05-default-pretokenizer.diff b/llm/patches/05-default-pretokenizer.diff index 0d40fc3ca..351bcaef1 100644 --- a/llm/patches/05-default-pretokenizer.diff +++ b/llm/patches/05-default-pretokenizer.diff @@ -1,8 +1,8 @@ diff --git a/src/llama.cpp b/src/llama.cpp -index a207451f..2ddf431d 100644 +index 88355971..dd7d41ed 100644 --- a/src/llama.cpp +++ b/src/llama.cpp -@@ -5347,16 +5347,7 @@ static void llm_load_vocab( +@@ -6083,16 +6083,7 @@ static void llm_load_vocab( if (vocab.type == LLAMA_VOCAB_TYPE_BPE) { vocab.tokenizer_add_space_prefix = false; vocab.tokenizer_clean_spaces = true; @@ -20,9 +20,9 @@ index a207451f..2ddf431d 100644 vocab.type_pre = LLAMA_VOCAB_PRE_TYPE_DEFAULT; } else if ( tokenizer_pre == "llama3" || -@@ -5443,7 +5434,8 @@ static void llm_load_vocab( - tokenizer_pre == "codeshell") { - vocab.type_pre = LLAMA_VOCAB_PRE_TYPE_CODESHELL; +@@ -6188,7 +6179,8 @@ static void llm_load_vocab( + tokenizer_pre == "exaone") { + vocab.type_pre = LLAMA_VOCAB_PRE_TYPE_EXAONE; } else { - throw std::runtime_error(format("unknown pre-tokenizer type: '%s'", tokenizer_pre.c_str())); + LLAMA_LOG_WARN("%s: missing or unrecognized pre-tokenizer type, using: 'default'\n", __func__); diff --git a/llm/patches/06-embeddings.diff b/llm/patches/06-embeddings.diff index a84e3b06c..f3c071cbb 100644 --- a/llm/patches/06-embeddings.diff +++ b/llm/patches/06-embeddings.diff @@ -1,37 +1,36 @@ diff --git a/src/llama.cpp b/src/llama.cpp -index 1fe2b9f7..a43312a7 100644 +index 88355971..d7db689b 100644 --- a/src/llama.cpp +++ b/src/llama.cpp -@@ -13689,7 +13689,7 @@ static size_t llama_output_reserve(llama_context & lctx, size_t n_outputs) { +@@ -15906,7 +15906,7 @@ static size_t llama_output_reserve(llama_context & lctx, size_t n_outputs) { const auto n_embd = hparams.n_embd; // TODO: use a per-batch flag for logits presence instead - const bool has_logits = !cparams.embeddings; + const bool has_logits = cparams.causal_attn; - const bool has_embd = lctx.is_encoding || (cparams.embeddings && (cparams.pooling_type == LLAMA_POOLING_TYPE_NONE)); + const bool has_embd = cparams.embeddings && (cparams.pooling_type == LLAMA_POOLING_TYPE_NONE); const size_t logits_size = has_logits ? n_vocab*n_outputs_max : 0; -@@ -13959,17 +13959,25 @@ static int llama_decode_internal( +@@ -16175,20 +16175,23 @@ static int llama_decode_internal( // no output res = nullptr; embd = nullptr; - } else if (cparams.embeddings) { -- res = nullptr; // do not extract logits for embedding case -- embd = gf->nodes[gf->n_nodes - 1]; -- if (strcmp(embd->name, "result_embd_pooled") != 0) { -- embd = gf->nodes[gf->n_nodes - 2]; +- res = nullptr; // do not extract logits for embedding case +- embd = nullptr; + } + + if (cparams.embeddings) { -+ for (int i = gf->n_nodes - 1; i >= 0; --i) { + for (int i = gf->n_nodes - 1; i >= 0; --i) { +- if (strcmp(gf->nodes[i]->name, "result_embd_pooled") == 0) { +- embd = gf->nodes[i]; + embd = gf->nodes[i]; + if (strcmp(embd->name, "result_embd_pooled") == 0) { -+ break; -+ } + break; + } } - GGML_ASSERT(strcmp(embd->name, "result_embd_pooled") == 0 && "missing embeddings tensor"); -- } else { -+ } else { +- GGML_ASSERT(embd != nullptr && "missing embeddings tensor"); + } else { embd = nullptr; // do not extract embeddings when not needed GGML_ASSERT(strcmp(res->name, "result_output") == 0 && "missing result_output tensor"); } @@ -39,7 +38,6 @@ index 1fe2b9f7..a43312a7 100644 + if (!cparams.causal_attn) { + res = nullptr; // do not extract logits when not needed + } -+ // LLAMA_LOG_INFO("graph build time: %.3f ms (%d nodes, %d leafs)\n", (ggml_time_us() - t_start_us)/1000.0, gf->n_nodes, gf->n_leafs); ggml_backend_sched_alloc_graph(lctx.sched, gf); diff --git a/llm/patches/09-lora.diff b/llm/patches/09-lora.diff deleted file mode 100644 index 219584767..000000000 --- a/llm/patches/09-lora.diff +++ /dev/null @@ -1,350 +0,0 @@ -diff --git a/common/common.cpp b/common/common.cpp -index 2e8374d5..70d0afde 100644 ---- a/common/common.cpp -+++ b/common/common.cpp -@@ -2110,9 +2110,21 @@ struct llama_init_result llama_init_from_gpt_params(gpt_params & params) { - loaded_la.adapter = llama_lora_adapter_init(model, la.path.c_str()); - if (loaded_la.adapter == nullptr) { - fprintf(stderr, "%s: error: failed to apply lora adapter '%s'\n", __func__, la.path.c_str()); -- llama_free(lctx); -- llama_free_model(model); -- return iparams; -+ -+ // if that fails, try loading as ggla for compatibility -+ int err = llama_model_apply_lora_from_file(model, -+ la.path.c_str(), -+ la.scale, -+ nullptr, -+ params.n_threads); -+ if (err != 0) { -+ fprintf(stderr, "%s: error: failed to apply lora adapter\n", __func__); -+ llama_free(lctx); -+ llama_free_model(model); -+ return iparams; -+ } else { -+ break; -+ } - } - iparams.lora_adapters.push_back(loaded_la); // copy to list of loaded adapters - } -diff --git a/include/llama.h b/include/llama.h -index 93fd77ca..b0fb37a6 100644 ---- a/include/llama.h -+++ b/include/llama.h -@@ -1160,6 +1160,20 @@ extern "C" { - - LLAMA_API void llama_dump_timing_info_yaml(FILE * stream, const struct llama_context * ctx); - -+ // Apply a LoRA adapter to a loaded model -+ // path_base_model is the path to a higher quality model to use as a base for -+ // the layers modified by the adapter. Can be NULL to use the current loaded model. -+ // The model needs to be reloaded before applying a new adapter, otherwise the adapter -+ // will be applied on top of the previous one -+ // Returns 0 on success -+ LLAMA_API int32_t llama_model_apply_lora_from_file( -+ const struct llama_model * model, -+ const char * path_lora, -+ float scale, -+ const char * path_base_model, -+ int32_t n_threads); -+ -+ - #ifdef __cplusplus - } - #endif -diff --git a/src/llama.cpp b/src/llama.cpp -index 80a0dd0f..9d7b0e17 100644 ---- a/src/llama.cpp -+++ b/src/llama.cpp -@@ -21880,3 +21880,290 @@ static void llama_log_callback_default(ggml_log_level level, const char * text, - fputs(text, stderr); - fflush(stderr); - } -+ -+static int llama_apply_lora_from_file_internal( -+ const struct llama_model & model, const char * path_lora, float scale, const char * path_base_model, int n_threads -+) { -+ LLAMA_LOG_INFO("%s: applying lora adapter from '%s' - please wait ...\n", __func__, path_lora); -+ -+ const int64_t t_start_lora_us = ggml_time_us(); -+ -+ llama_file fin(path_lora, "rb"); -+ -+ // verify magic and version -+ { -+ uint32_t magic = fin.read_u32(); -+ if (magic != LLAMA_FILE_MAGIC_GGLA) { -+ LLAMA_LOG_ERROR("%s: bad file magic\n", __func__); -+ return 1; -+ } -+ -+ uint32_t format_version = fin.read_u32(); -+ if (format_version != 1) { -+ LLAMA_LOG_ERROR("%s: unsupported file version\n", __func__ ); -+ return 1; -+ } -+ } -+ -+ int32_t lora_r = fin.read_u32(); -+ int32_t lora_alpha = fin.read_u32(); -+ float scaling = scale * (float)lora_alpha / (float)lora_r; -+ -+ LLAMA_LOG_INFO("%s: r = %d, alpha = %d, scaling = %.2f\n", __func__, lora_r, lora_alpha, scaling); -+ -+ // load base model -+ std::unique_ptr ml; -+ if (path_base_model) { -+ LLAMA_LOG_INFO("%s: loading base model from '%s'\n", __func__, path_base_model); -+ ml.reset(new llama_model_loader(path_base_model, /*use_mmap*/ true, /*check_tensors*/ false, /*kv_overrides*/ nullptr)); -+ ml->init_mappings(/*prefetch*/ false); // no prefetching -+ } -+ -+ struct tensor_meta { -+ std::string name; -+ ggml_type type; -+ int32_t ne[2]; -+ size_t offset; -+ }; -+ std::map tensor_meta_map; -+ -+ // load all tensor meta -+ while (true) { -+ if (fin.tell() == fin.size) { -+ // eof -+ break; -+ } -+ -+ int32_t n_dims; -+ int32_t name_len; -+ int32_t ftype; -+ -+ fin.read_raw(&n_dims, sizeof(n_dims)); -+ fin.read_raw(&name_len, sizeof(name_len)); -+ fin.read_raw(&ftype, sizeof(ftype)); -+ -+ if (n_dims != 1 && n_dims != 2) { -+ LLAMA_LOG_ERROR("%s: unsupported tensor dimension %d\n", __func__, n_dims); -+ return 1; -+ } -+ -+ int32_t ne[2] = { 1, 1 }; -+ for (int i = 0; i < n_dims; ++i) { -+ fin.read_raw(&ne[i], sizeof(ne[i])); -+ } -+ -+ std::string name; -+ { -+ GGML_ASSERT(name_len < GGML_MAX_NAME); -+ char buf[GGML_MAX_NAME]; -+ fin.read_raw(buf, name_len); -+ name = std::string(buf, name_len); -+ } -+ -+ // check for lora suffix -+ std::string lora_suffix; -+ if (name.length() > 6) { -+ lora_suffix = name.substr(name.length() - 6); -+ } -+ if (lora_suffix != ".loraA" && lora_suffix != ".loraB") { -+ LLAMA_LOG_ERROR("%s: error: '%s' is not a lora tensor\n", __func__, name.c_str()); -+ return 1; -+ } -+ -+ // tensor type -+ ggml_type wtype; -+ switch (ftype) { -+ case 0: wtype = GGML_TYPE_F32; break; -+ case 1: wtype = GGML_TYPE_F16; break; -+ default: -+ { -+ LLAMA_LOG_ERROR("%s: invalid tensor data type '%d'\n", -+ __func__, ftype); -+ return 1; -+ } -+ } -+ -+ // data offset -+ size_t offset = fin.tell(); -+ offset = (offset + 31) & -32; -+ -+ // skip tensor data -+ fin.seek(offset + ggml_row_size(wtype, ne[0]) * ne[1], SEEK_SET); -+ -+ tensor_meta_map.emplace(name, tensor_meta{ name, wtype, { ne[0], ne[1] }, offset }); -+ } -+ -+ bool warned = false; -+ int n_tensors = 0; -+ -+ // apply -+ ggml_backend_t backend_cpu = ggml_backend_cpu_init(); -+ if (backend_cpu == nullptr) { -+ LLAMA_LOG_ERROR("%s: error: failed to initialize cpu backend\n", __func__); -+ return 1; -+ } -+ ggml_backend_cpu_set_n_threads(backend_cpu, n_threads); -+ -+ std::vector> read_buf; -+ for (const auto & it : model.tensors_by_name) { -+ const std::string & base_name = it.first; -+ ggml_tensor * model_t = it.second; -+ -+ if (tensor_meta_map.find(base_name + ".loraA") == tensor_meta_map.end() || -+ tensor_meta_map.find(base_name + ".loraB") == tensor_meta_map.end()) { -+ continue; -+ } -+ -+ tensor_meta & metaA = tensor_meta_map.at(base_name + ".loraA"); -+ tensor_meta & metaB = tensor_meta_map.at(base_name + ".loraB"); -+ -+ ggml_init_params lora_init_params = { -+ /* .mem_size */ ggml_tensor_overhead()*128 + ggml_graph_overhead(), -+ /* .mem_buffer */ nullptr, -+ /* .no_alloc */ true, -+ }; -+ ggml_context * lora_ctx = ggml_init(lora_init_params); -+ if (lora_ctx == nullptr) { -+ LLAMA_LOG_ERROR("%s: error: failed to initialize lora context\n", __func__); -+ ggml_backend_free(backend_cpu); -+ return 1; -+ } -+ -+ // create tensors -+ ggml_tensor * loraA = ggml_new_tensor_2d(lora_ctx, metaA.type, metaA.ne[0], metaA.ne[1]); -+ ggml_tensor * loraB = ggml_new_tensor_2d(lora_ctx, metaB.type, metaB.ne[0], metaB.ne[1]); -+ ggml_set_name(loraA, metaA.name.c_str()); -+ ggml_set_name(loraB, metaB.name.c_str()); -+ -+ ggml_tensor * base_t; -+ if (ml) { -+ if (!ml->get_tensor_meta(base_name.c_str())) { -+ LLAMA_LOG_ERROR("%s: error: tensor '%s' not found in base model\n", __func__, base_name.c_str()); -+ return 1; -+ } -+ base_t = ggml_dup_tensor(lora_ctx, ml->get_tensor_meta(base_name.c_str())); -+ } else { -+ base_t = ggml_dup_tensor(lora_ctx, model_t); -+ } -+ ggml_set_name(base_t, base_name.c_str()); -+ -+ // allocate in backend buffer -+ ggml_backend_buffer_t lora_buf = ggml_backend_alloc_ctx_tensors_from_buft(lora_ctx, ggml_backend_cpu_buffer_type()); -+ if (lora_buf == nullptr) { -+ LLAMA_LOG_ERROR("%s: error: failed to allocate lora tensors\n", __func__); -+ return 1; -+ } -+ -+ // load tensor data -+ auto load_tensor = [&read_buf, &fin](const tensor_meta & tensor_meta, ggml_tensor * tensor) { -+ read_buf.resize(ggml_nbytes(tensor)); -+ fin.seek(tensor_meta.offset, SEEK_SET); -+ fin.read_raw(read_buf.data(), ggml_nbytes(tensor)); -+ ggml_backend_tensor_set(tensor, read_buf.data(), 0, read_buf.size()); -+ }; -+ load_tensor(metaA, loraA); -+ load_tensor(metaB, loraB); -+ -+ // load base model tensor data -+ if (ml) { -+ ml->load_data_for(base_t); -+ } else { -+ ggml_backend_tensor_copy(model_t, base_t); -+ } -+ -+ if (ggml_is_quantized(base_t->type) && !warned) { -+ LLAMA_LOG_WARN("%s: warning: using a lora adapter with a quantized model may result in poor quality, " -+ "use a f16 or f32 base model with --lora-base\n", __func__); -+ warned = true; -+ } -+ -+ if (base_t->ne[0] != loraA->ne[1] || base_t->ne[1] != loraB->ne[1]) { -+ LLAMA_LOG_ERROR("%s: incompatible tensor dimensions (%" PRId64 " and %" PRId64 ");" -+ " are you sure that this adapter is for this model?\n", __func__, base_t->ne[0], loraA->ne[1]); -+ ggml_free(lora_ctx); -+ ggml_backend_buffer_free(lora_buf); -+ ggml_backend_free(backend_cpu); -+ return 1; -+ } -+ -+ auto build_lora_graph = [&]() { -+ // w = w + BA*s -+ ggml_tensor * BA = ggml_mul_mat(lora_ctx, loraA, loraB); -+ ggml_set_name(BA, "BA"); -+ -+ if (scaling != 1.0f) { -+ BA = ggml_scale(lora_ctx, BA, scaling); -+ ggml_set_name(BA, "BA_scaled"); -+ } -+ -+ ggml_tensor * r; -+ r = ggml_add_inplace(lora_ctx, base_t, BA); -+ ggml_set_name(r, "r_add"); -+ -+ if (base_t->type != model_t->type) { -+ // convert the result to the model type -+ r = ggml_cast(lora_ctx, r, model_t->type); -+ ggml_set_name(r, "r_cast"); -+ } -+ -+ return r; -+ }; -+ -+ ggml_cgraph * gf = ggml_new_graph(lora_ctx); -+ ggml_tensor * r = build_lora_graph(); -+ ggml_build_forward_expand(gf, r); -+ -+ ggml_backend_buffer_t graph_buf = ggml_backend_alloc_ctx_tensors_from_buft(lora_ctx, ggml_backend_cpu_buffer_type()); -+ if (graph_buf == nullptr) { -+ LLAMA_LOG_ERROR("%s: error: failed to allocate graph tensors\n", __func__); -+ ggml_free(lora_ctx); -+ ggml_backend_buffer_free(lora_buf); -+ ggml_backend_free(backend_cpu); -+ return 1; -+ } -+ -+ ggml_backend_graph_compute(backend_cpu, gf); -+ -+ ggml_backend_tensor_set(model_t, r->data, 0, ggml_nbytes(r)); -+ -+#if 0 -+ // TODO: use scheduler with fallback to CPU for less copies between CPU and GPU -+ //ggml_backend_sched_t sched = ggml_backend_sched_new(backends.data(), backends.size(), GGML_DEFAULT_GRAPH_SIZE); -+ -+ // sched compute -+ ggml_build_forward_expand(gf, build_graph()); -+ ggml_backend_sched_init_measure(sched, gf); -+ -+ // create the graph again, since the previous one was destroyed by the measure -+ ggml_graph_clear(gf); -+ ggml_build_forward_expand(gf, build_graph()); -+ ggml_backend_sched_graph_compute(sched, gf); -+ ggml_backend_sched_free(sched); -+#endif -+ -+ ggml_backend_buffer_free(lora_buf); -+ ggml_backend_buffer_free(graph_buf); -+ ggml_free(lora_ctx); -+ -+ n_tensors++; -+ if (n_tensors % 4 == 0) { -+ LLAMA_LOG_INFO("."); -+ } -+ } -+ -+ ggml_backend_free(backend_cpu); -+ -+ const int64_t t_lora_us = ggml_time_us() - t_start_lora_us; -+ LLAMA_LOG_INFO(" done (%.2f ms)\n", t_lora_us / 1000.0); -+ -+ return 0; -+} -+ -+int32_t llama_model_apply_lora_from_file(const struct llama_model * model, const char * path_lora, float scale, const char * path_base_model, int32_t n_threads) { -+ try { -+ return llama_apply_lora_from_file_internal(*model, path_lora, scale, path_base_model, n_threads); -+ } catch (const std::exception & err) { -+ LLAMA_LOG_ERROR("%s: failed to apply lora adapter: %s\n", __func__, err.what()); -+ return 1; -+ } -+} -\ No newline at end of file \ No newline at end of file diff --git a/llm/patches/11-phi3-sliding-window.diff b/llm/patches/11-phi3-sliding-window.diff deleted file mode 100644 index fde3dd219..000000000 --- a/llm/patches/11-phi3-sliding-window.diff +++ /dev/null @@ -1,43 +0,0 @@ -From 6eedae4cf2fcc8015dac79cb3f28f61fcabacab2 Mon Sep 17 00:00:00 2001 -From: Michael Yang -Date: Wed, 31 Jul 2024 14:57:04 -0700 -Subject: [PATCH] phi3 sliding window - ---- - src/llama.cpp | 6 +++--- - 1 file changed, 3 insertions(+), 3 deletions(-) - -diff --git a/src/llama.cpp b/src/llama.cpp -index a207451f..f2872d4e 100644 ---- a/src/llama.cpp -+++ b/src/llama.cpp -@@ -4893,7 +4893,7 @@ static void llm_load_hparams( - } break; - case LLM_ARCH_PHI3: - { -- ml.get_key(LLM_KV_ATTENTION_SLIDING_WINDOW, hparams.n_swa); -+ ml.get_key(LLM_KV_ATTENTION_SLIDING_WINDOW, hparams.n_swa, false); - ml.get_key(LLM_KV_ATTENTION_LAYERNORM_RMS_EPS, hparams.f_norm_rms_eps); - - switch (hparams.n_layer) { -@@ -10762,7 +10762,7 @@ struct llm_build_context { - struct ggml_tensor * inp_pos = build_inp_pos(); - - // KQ_mask (mask for 1 head, it will be broadcasted to all heads) -- struct ggml_tensor * KQ_mask_swa = build_inp_KQ_mask_swa(); -+ struct ggml_tensor * KQ_mask = hparams.n_swa > 0 ? build_inp_KQ_mask_swa() : build_inp_KQ_mask(); - - for (int il = 0; il < n_layer; ++il) { - auto residual = inpL; -@@ -10820,7 +10820,7 @@ struct llm_build_context { - - cur = llm_build_kv(ctx0, lctx, kv_self, gf, - model.layers[il].wo, model.layers[il].bo, -- Kcur, Vcur, Qcur, KQ_mask_swa, n_tokens, kv_head, n_kv, 1.0f, cb, il); -+ Kcur, Vcur, Qcur, KQ_mask, n_tokens, kv_head, n_kv, 1.0f, cb, il); - } - - if (il == n_layer - 1) { --- -2.45.2 - From 491fc312ae1e5a7a2ea0a02546b2ffa61675dd1a Mon Sep 17 00:00:00 2001 From: Viz <7476271+kspviswa@users.noreply.github.com> Date: Tue, 3 Sep 2024 23:10:53 -0400 Subject: [PATCH 317/384] readme: add PyOllaMx project (#6624) --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index fee01baa0..4fc02d10c 100644 --- a/README.md +++ b/README.md @@ -306,7 +306,8 @@ See the [API documentation](./docs/api.md) for all endpoints. - [Go-CREW](https://www.jonathanhecl.com/go-crew/) (Powerful Offline RAG in Golang) - [PartCAD](https://github.com/openvmp/partcad/) (CAD model generation with OpenSCAD and CadQuery) - [Ollama4j Web UI](https://github.com/ollama4j/ollama4j-web-ui) - Java-based Web UI for Ollama built with Vaadin, Spring Boot and Ollama4j - +- [PyOllaMx](https://github.com/kspviswa/pyOllaMx) - macOS application capable of chatting with both Ollama and Apple MLX models. + ### Terminal - [oterm](https://github.com/ggozad/oterm) From 27bcce6d9fb1e002ef4393ff48d6cadb5b29da41 Mon Sep 17 00:00:00 2001 From: Sam Date: Wed, 4 Sep 2024 23:32:26 +1000 Subject: [PATCH 318/384] readme: add claude-dev to community integrations (#6630) --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 4fc02d10c..edd8ab1bf 100644 --- a/README.md +++ b/README.md @@ -307,6 +307,7 @@ See the [API documentation](./docs/api.md) for all endpoints. - [PartCAD](https://github.com/openvmp/partcad/) (CAD model generation with OpenSCAD and CadQuery) - [Ollama4j Web UI](https://github.com/ollama4j/ollama4j-web-ui) - Java-based Web UI for Ollama built with Vaadin, Spring Boot and Ollama4j - [PyOllaMx](https://github.com/kspviswa/pyOllaMx) - macOS application capable of chatting with both Ollama and Apple MLX models. +- [Claude Dev](https://github.com/saoudrizwan/claude-dev) - VSCode extension for multi-file/whole-repo coding ### Terminal From 7d89e48f5c1a8e719d5b8d154151c39685f50919 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Erkin=20Alp=20G=C3=BCney?= Date: Wed, 4 Sep 2024 16:34:53 +0300 Subject: [PATCH 319/384] install.sh: update instructions to use WSL2 (#6450) --- scripts/install.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/install.sh b/scripts/install.sh index 25f57565a..5a212975e 100644 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -38,7 +38,7 @@ IS_WSL2=false KERN=$(uname -r) case "$KERN" in *icrosoft*WSL2 | *icrosoft*wsl2) IS_WSL2=true;; - *icrosoft) error "Microsoft WSL1 is not currently supported. Please upgrade to WSL2 with 'wsl --set-version 2'" ;; + *icrosoft) error "Microsoft WSL1 is not currently supported. Please use WSL2 with 'wsl --set-version 2'" ;; *) ;; esac From 369479cc3068b5f2b28cdc766de3c27cff7e2f7b Mon Sep 17 00:00:00 2001 From: Carter <102479896+Carter907@users.noreply.github.com> Date: Wed, 4 Sep 2024 09:42:33 -0400 Subject: [PATCH 320/384] docs: fix spelling error (#6391) change "dorrect" to "correct" --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 7f12a0fc8..f003a69da 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -18,7 +18,7 @@ See the [development documentation](./docs/development.md) for instructions on h * New features: new features (e.g. API fields, environment variables) add surface area to Ollama and make it harder to maintain in the long run as they cannot be removed without potentially breaking users in the future. * Refactoring: large code improvements are important, but can be harder or take longer to review and merge. -* Documentation: small updates to fill in or dorrect missing documentation is helpful, however large documentation additions can be hard to maintain over time. +* Documentation: small updates to fill in or correct missing documentation is helpful, however large documentation additions can be hard to maintain over time. ### Issues that may not be accepted From 93eb43d020665cce0f1de17cc108224ae83b0605 Mon Sep 17 00:00:00 2001 From: Mitar Date: Wed, 4 Sep 2024 16:52:46 +0200 Subject: [PATCH 321/384] readme: add Go fun package (#6421) --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index edd8ab1bf..a6f254fe7 100644 --- a/README.md +++ b/README.md @@ -378,6 +378,7 @@ See the [API documentation](./docs/api.md) for all endpoints. - [PromptingTools.jl](https://github.com/svilupp/PromptingTools.jl) with an [example](https://svilupp.github.io/PromptingTools.jl/dev/examples/working_with_ollama) - [LlamaScript](https://github.com/Project-Llama/llamascript) - [Ollamaclient for Golang](https://github.com/xyproto/ollamaclient) +- [High-level function abstraction in Go](https://gitlab.com/tozd/go/fun) ### Mobile From 5b55379651f4cdbd5efcc397d894c3d008c96a5a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BA=A2=E5=A5=8B=E7=8C=AB?= Date: Wed, 4 Sep 2024 22:53:36 +0800 Subject: [PATCH 322/384] readme: add Cherry Studio to community integrations (#6633) --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index a6f254fe7..afcda18fe 100644 --- a/README.md +++ b/README.md @@ -308,6 +308,7 @@ See the [API documentation](./docs/api.md) for all endpoints. - [Ollama4j Web UI](https://github.com/ollama4j/ollama4j-web-ui) - Java-based Web UI for Ollama built with Vaadin, Spring Boot and Ollama4j - [PyOllaMx](https://github.com/kspviswa/pyOllaMx) - macOS application capable of chatting with both Ollama and Apple MLX models. - [Claude Dev](https://github.com/saoudrizwan/claude-dev) - VSCode extension for multi-file/whole-repo coding +- [Cherry Studio](https://github.com/kangfenmao/cherry-studio) (Desktop client with Ollama support) ### Terminal From f36ebfb47835075e3b9d01a6c1a082653c24d9ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Te=C3=AFlo=20M?= <39594466+teilomillet@users.noreply.github.com> Date: Wed, 4 Sep 2024 20:19:41 +0200 Subject: [PATCH 323/384] readme: add gollm to the list of community libraries (#6099) --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index afcda18fe..56f2e1d84 100644 --- a/README.md +++ b/README.md @@ -378,6 +378,7 @@ See the [API documentation](./docs/api.md) for all endpoints. - [Portkey](https://portkey.ai/docs/welcome/integration-guides/ollama) - [PromptingTools.jl](https://github.com/svilupp/PromptingTools.jl) with an [example](https://svilupp.github.io/PromptingTools.jl/dev/examples/working_with_ollama) - [LlamaScript](https://github.com/Project-Llama/llamascript) +- [Gollm](https://docs.gollm.co/examples/ollama-example) - [Ollamaclient for Golang](https://github.com/xyproto/ollamaclient) - [High-level function abstraction in Go](https://gitlab.com/tozd/go/fun) From 133770a5484e4ff2e22a49b4728a4ddac7e19db4 Mon Sep 17 00:00:00 2001 From: Tomoya Fujita Date: Wed, 4 Sep 2024 11:45:09 -0700 Subject: [PATCH 324/384] docs: add group to manual Linux isntructions and verify service is running (#6430) --- docs/linux.md | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/docs/linux.md b/docs/linux.md index fbaf48454..46c17a20d 100644 --- a/docs/linux.md +++ b/docs/linux.md @@ -35,10 +35,11 @@ curl -fsSL https://ollama.com/download/ollama-linux-amd64-rocm.tgz | sudo tar zx ### Adding Ollama as a startup service (recommended) -Create a user for Ollama: +Create a user and group for Ollama: ```bash -sudo useradd -r -s /bin/false -m -d /usr/share/ollama ollama +sudo useradd -r -s /bin/false -U -m -d /usr/share/ollama ollama +sudo usermod -a -G ollama $(whoami) ``` Create a service file in `/etc/systemd/system/ollama.service`: @@ -54,6 +55,7 @@ User=ollama Group=ollama Restart=always RestartSec=3 +Environment="PATH=$PATH" [Install] WantedBy=default.target @@ -83,10 +85,11 @@ Make sure to install ROCm v6 ### Start Ollama -Start Ollama using `systemd`: +Start Ollama and verify it is running: ```bash sudo systemctl start ollama +sudo systemctl status ollama ``` ## Update From c18ff18b2c95a8a39dcd7148c0311e8409221ec5 Mon Sep 17 00:00:00 2001 From: Rune Berg <1runeberg@users.noreply.github.com> Date: Thu, 5 Sep 2024 09:26:02 +1200 Subject: [PATCH 325/384] readme: add confichat to community integrations (#6378) --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 56f2e1d84..ea2797614 100644 --- a/README.md +++ b/README.md @@ -309,7 +309,8 @@ See the [API documentation](./docs/api.md) for all endpoints. - [PyOllaMx](https://github.com/kspviswa/pyOllaMx) - macOS application capable of chatting with both Ollama and Apple MLX models. - [Claude Dev](https://github.com/saoudrizwan/claude-dev) - VSCode extension for multi-file/whole-repo coding - [Cherry Studio](https://github.com/kangfenmao/cherry-studio) (Desktop client with Ollama support) - +- [ConfiChat](https://github.com/1runeberg/confichat) (Lightweight, standalone, multi-platform, and privacy focused LLM chat interface with optional encryption) + ### Terminal - [oterm](https://github.com/ggozad/oterm) @@ -386,6 +387,7 @@ See the [API documentation](./docs/api.md) for all endpoints. - [Enchanted](https://github.com/AugustDev/enchanted) - [Maid](https://github.com/Mobile-Artificial-Intelligence/maid) +- [ConfiChat](https://github.com/1runeberg/confichat) (Lightweight, standalone, multi-platform, and privacy focused LLM chat interface with optional encryption) ### Extensions & Plugins From bbe7b96ded600fa429639c9de97f5334c414b6c8 Mon Sep 17 00:00:00 2001 From: Pascal Patry Date: Wed, 4 Sep 2024 19:34:42 -0400 Subject: [PATCH 326/384] llm: use json.hpp from common (#6642) --- llm/ext_server/CMakeLists.txt | 2 +- llm/ext_server/json.hpp | 24596 -------------------------------- 2 files changed, 1 insertion(+), 24597 deletions(-) delete mode 100644 llm/ext_server/json.hpp diff --git a/llm/ext_server/CMakeLists.txt b/llm/ext_server/CMakeLists.txt index 90fd0ef25..88c8b03d7 100644 --- a/llm/ext_server/CMakeLists.txt +++ b/llm/ext_server/CMakeLists.txt @@ -2,7 +2,7 @@ set(TARGET ollama_llama_server) option(LLAMA_SERVER_VERBOSE "Build verbose logging option for Server" ON) set(LLAMA_SERVER_LDFLAGS $ENV{LLAMA_SERVER_LDFLAGS}) include_directories(${CMAKE_CURRENT_SOURCE_DIR}) -add_executable(${TARGET} server.cpp utils.hpp json.hpp httplib.h) +add_executable(${TARGET} server.cpp utils.hpp httplib.h) install(TARGETS ${TARGET} RUNTIME) target_compile_definitions(${TARGET} PRIVATE SERVER_VERBOSE=$ diff --git a/llm/ext_server/json.hpp b/llm/ext_server/json.hpp deleted file mode 100644 index ea945f346..000000000 --- a/llm/ext_server/json.hpp +++ /dev/null @@ -1,24596 +0,0 @@ -// __ _____ _____ _____ -// __| | __| | | | JSON for Modern C++ -// | | |__ | | | | | | version 3.11.2 -// |_____|_____|_____|_|___| https://github.com/nlohmann/json -// -// SPDX-FileCopyrightText: 2013-2022 Niels Lohmann -// SPDX-License-Identifier: MIT - -/****************************************************************************\ - * Note on documentation: The source files contain links to the online * - * documentation of the public API at https://json.nlohmann.me. This URL * - * contains the most recent documentation and should also be applicable to * - * previous versions; documentation for deprecated functions is not * - * removed, but marked deprecated. See "Generate documentation" section in * - * file docs/README.md. * -\****************************************************************************/ - -#ifndef INCLUDE_NLOHMANN_JSON_HPP_ -#define INCLUDE_NLOHMANN_JSON_HPP_ - -#include // all_of, find, for_each -#include // nullptr_t, ptrdiff_t, size_t -#include // hash, less -#include // initializer_list -#ifndef JSON_NO_IO - #include // istream, ostream -#endif // JSON_NO_IO -#include // random_access_iterator_tag -#include // unique_ptr -#include // accumulate -#include // string, stoi, to_string -#include // declval, forward, move, pair, swap -#include // vector - -// #include -// __ _____ _____ _____ -// __| | __| | | | JSON for Modern C++ -// | | |__ | | | | | | version 3.11.2 -// |_____|_____|_____|_|___| https://github.com/nlohmann/json -// -// SPDX-FileCopyrightText: 2013-2022 Niels Lohmann -// SPDX-License-Identifier: MIT - - - -#include - -// #include -// __ _____ _____ _____ -// __| | __| | | | JSON for Modern C++ -// | | |__ | | | | | | version 3.11.2 -// |_____|_____|_____|_|___| https://github.com/nlohmann/json -// -// SPDX-FileCopyrightText: 2013-2022 Niels Lohmann -// SPDX-License-Identifier: MIT - - - -// This file contains all macro definitions affecting or depending on the ABI - -#ifndef JSON_SKIP_LIBRARY_VERSION_CHECK - #if defined(NLOHMANN_JSON_VERSION_MAJOR) && defined(NLOHMANN_JSON_VERSION_MINOR) && defined(NLOHMANN_JSON_VERSION_PATCH) - #if NLOHMANN_JSON_VERSION_MAJOR != 3 || NLOHMANN_JSON_VERSION_MINOR != 11 || NLOHMANN_JSON_VERSION_PATCH != 2 - #warning "Already included a different version of the library!" - #endif - #endif -#endif - -#define NLOHMANN_JSON_VERSION_MAJOR 3 // NOLINT(modernize-macro-to-enum) -#define NLOHMANN_JSON_VERSION_MINOR 11 // NOLINT(modernize-macro-to-enum) -#define NLOHMANN_JSON_VERSION_PATCH 2 // NOLINT(modernize-macro-to-enum) - -#ifndef JSON_DIAGNOSTICS - #define JSON_DIAGNOSTICS 0 -#endif - -#ifndef JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON - #define JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON 0 -#endif - -#if JSON_DIAGNOSTICS - #define NLOHMANN_JSON_ABI_TAG_DIAGNOSTICS _diag -#else - #define NLOHMANN_JSON_ABI_TAG_DIAGNOSTICS -#endif - -#if JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON - #define NLOHMANN_JSON_ABI_TAG_LEGACY_DISCARDED_VALUE_COMPARISON _ldvcmp -#else - #define NLOHMANN_JSON_ABI_TAG_LEGACY_DISCARDED_VALUE_COMPARISON -#endif - -#ifndef NLOHMANN_JSON_NAMESPACE_NO_VERSION - #define NLOHMANN_JSON_NAMESPACE_NO_VERSION 0 -#endif - -// Construct the namespace ABI tags component -#define NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b) json_abi ## a ## b -#define NLOHMANN_JSON_ABI_TAGS_CONCAT(a, b) \ - NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b) - -#define NLOHMANN_JSON_ABI_TAGS \ - NLOHMANN_JSON_ABI_TAGS_CONCAT( \ - NLOHMANN_JSON_ABI_TAG_DIAGNOSTICS, \ - NLOHMANN_JSON_ABI_TAG_LEGACY_DISCARDED_VALUE_COMPARISON) - -// Construct the namespace version component -#define NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT_EX(major, minor, patch) \ - _v ## major ## _ ## minor ## _ ## patch -#define NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT(major, minor, patch) \ - NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT_EX(major, minor, patch) - -#if NLOHMANN_JSON_NAMESPACE_NO_VERSION -#define NLOHMANN_JSON_NAMESPACE_VERSION -#else -#define NLOHMANN_JSON_NAMESPACE_VERSION \ - NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT(NLOHMANN_JSON_VERSION_MAJOR, \ - NLOHMANN_JSON_VERSION_MINOR, \ - NLOHMANN_JSON_VERSION_PATCH) -#endif - -// Combine namespace components -#define NLOHMANN_JSON_NAMESPACE_CONCAT_EX(a, b) a ## b -#define NLOHMANN_JSON_NAMESPACE_CONCAT(a, b) \ - NLOHMANN_JSON_NAMESPACE_CONCAT_EX(a, b) - -#ifndef NLOHMANN_JSON_NAMESPACE -#define NLOHMANN_JSON_NAMESPACE \ - nlohmann::NLOHMANN_JSON_NAMESPACE_CONCAT( \ - NLOHMANN_JSON_ABI_TAGS, \ - NLOHMANN_JSON_NAMESPACE_VERSION) -#endif - -#ifndef NLOHMANN_JSON_NAMESPACE_BEGIN -#define NLOHMANN_JSON_NAMESPACE_BEGIN \ - namespace nlohmann \ - { \ - inline namespace NLOHMANN_JSON_NAMESPACE_CONCAT( \ - NLOHMANN_JSON_ABI_TAGS, \ - NLOHMANN_JSON_NAMESPACE_VERSION) \ - { -#endif - -#ifndef NLOHMANN_JSON_NAMESPACE_END -#define NLOHMANN_JSON_NAMESPACE_END \ - } /* namespace (inline namespace) NOLINT(readability/namespace) */ \ - } // namespace nlohmann -#endif - -// #include -// __ _____ _____ _____ -// __| | __| | | | JSON for Modern C++ -// | | |__ | | | | | | version 3.11.2 -// |_____|_____|_____|_|___| https://github.com/nlohmann/json -// -// SPDX-FileCopyrightText: 2013-2022 Niels Lohmann -// SPDX-License-Identifier: MIT - - - -#include // transform -#include // array -#include // forward_list -#include // inserter, front_inserter, end -#include // map -#include // string -#include // tuple, make_tuple -#include // is_arithmetic, is_same, is_enum, underlying_type, is_convertible -#include // unordered_map -#include // pair, declval -#include // valarray - -// #include -// __ _____ _____ _____ -// __| | __| | | | JSON for Modern C++ -// | | |__ | | | | | | version 3.11.2 -// |_____|_____|_____|_|___| https://github.com/nlohmann/json -// -// SPDX-FileCopyrightText: 2013-2022 Niels Lohmann -// SPDX-License-Identifier: MIT - - - -#include // nullptr_t -#include // exception -#include // runtime_error -#include // to_string -#include // vector - -// #include -// __ _____ _____ _____ -// __| | __| | | | JSON for Modern C++ -// | | |__ | | | | | | version 3.11.2 -// |_____|_____|_____|_|___| https://github.com/nlohmann/json -// -// SPDX-FileCopyrightText: 2013-2022 Niels Lohmann -// SPDX-License-Identifier: MIT - - - -#include // array -#include // size_t -#include // uint8_t -#include // string - -// #include -// __ _____ _____ _____ -// __| | __| | | | JSON for Modern C++ -// | | |__ | | | | | | version 3.11.2 -// |_____|_____|_____|_|___| https://github.com/nlohmann/json -// -// SPDX-FileCopyrightText: 2013-2022 Niels Lohmann -// SPDX-License-Identifier: MIT - - - -#include // declval, pair -// #include -// __ _____ _____ _____ -// __| | __| | | | JSON for Modern C++ -// | | |__ | | | | | | version 3.11.2 -// |_____|_____|_____|_|___| https://github.com/nlohmann/json -// -// SPDX-FileCopyrightText: 2013-2022 Niels Lohmann -// SPDX-License-Identifier: MIT - - - -#include - -// #include -// __ _____ _____ _____ -// __| | __| | | | JSON for Modern C++ -// | | |__ | | | | | | version 3.11.2 -// |_____|_____|_____|_|___| https://github.com/nlohmann/json -// -// SPDX-FileCopyrightText: 2013-2022 Niels Lohmann -// SPDX-License-Identifier: MIT - - - -// #include - - -NLOHMANN_JSON_NAMESPACE_BEGIN -namespace detail -{ - -template struct make_void -{ - using type = void; -}; -template using void_t = typename make_void::type; - -} // namespace detail -NLOHMANN_JSON_NAMESPACE_END - - -NLOHMANN_JSON_NAMESPACE_BEGIN -namespace detail -{ - -// https://en.cppreference.com/w/cpp/experimental/is_detected -struct nonesuch -{ - nonesuch() = delete; - ~nonesuch() = delete; - nonesuch(nonesuch const&) = delete; - nonesuch(nonesuch const&&) = delete; - void operator=(nonesuch const&) = delete; - void operator=(nonesuch&&) = delete; -}; - -template class Op, - class... Args> -struct detector -{ - using value_t = std::false_type; - using type = Default; -}; - -template class Op, class... Args> -struct detector>, Op, Args...> -{ - using value_t = std::true_type; - using type = Op; -}; - -template class Op, class... Args> -using is_detected = typename detector::value_t; - -template class Op, class... Args> -struct is_detected_lazy : is_detected { }; - -template class Op, class... Args> -using detected_t = typename detector::type; - -template class Op, class... Args> -using detected_or = detector; - -template class Op, class... Args> -using detected_or_t = typename detected_or::type; - -template class Op, class... Args> -using is_detected_exact = std::is_same>; - -template class Op, class... Args> -using is_detected_convertible = - std::is_convertible, To>; - -} // namespace detail -NLOHMANN_JSON_NAMESPACE_END - -// #include - - -// __ _____ _____ _____ -// __| | __| | | | JSON for Modern C++ -// | | |__ | | | | | | version 3.11.2 -// |_____|_____|_____|_|___| https://github.com/nlohmann/json -// -// SPDX-FileCopyrightText: 2013-2022 Niels Lohmann -// SPDX-FileCopyrightText: 2016-2021 Evan Nemerson -// SPDX-License-Identifier: MIT - -/* Hedley - https://nemequ.github.io/hedley - * Created by Evan Nemerson - */ - -#if !defined(JSON_HEDLEY_VERSION) || (JSON_HEDLEY_VERSION < 15) -#if defined(JSON_HEDLEY_VERSION) - #undef JSON_HEDLEY_VERSION -#endif -#define JSON_HEDLEY_VERSION 15 - -#if defined(JSON_HEDLEY_STRINGIFY_EX) - #undef JSON_HEDLEY_STRINGIFY_EX -#endif -#define JSON_HEDLEY_STRINGIFY_EX(x) #x - -#if defined(JSON_HEDLEY_STRINGIFY) - #undef JSON_HEDLEY_STRINGIFY -#endif -#define JSON_HEDLEY_STRINGIFY(x) JSON_HEDLEY_STRINGIFY_EX(x) - -#if defined(JSON_HEDLEY_CONCAT_EX) - #undef JSON_HEDLEY_CONCAT_EX -#endif -#define JSON_HEDLEY_CONCAT_EX(a,b) a##b - -#if defined(JSON_HEDLEY_CONCAT) - #undef JSON_HEDLEY_CONCAT -#endif -#define JSON_HEDLEY_CONCAT(a,b) JSON_HEDLEY_CONCAT_EX(a,b) - -#if defined(JSON_HEDLEY_CONCAT3_EX) - #undef JSON_HEDLEY_CONCAT3_EX -#endif -#define JSON_HEDLEY_CONCAT3_EX(a,b,c) a##b##c - -#if defined(JSON_HEDLEY_CONCAT3) - #undef JSON_HEDLEY_CONCAT3 -#endif -#define JSON_HEDLEY_CONCAT3(a,b,c) JSON_HEDLEY_CONCAT3_EX(a,b,c) - -#if defined(JSON_HEDLEY_VERSION_ENCODE) - #undef JSON_HEDLEY_VERSION_ENCODE -#endif -#define JSON_HEDLEY_VERSION_ENCODE(major,minor,revision) (((major) * 1000000) + ((minor) * 1000) + (revision)) - -#if defined(JSON_HEDLEY_VERSION_DECODE_MAJOR) - #undef JSON_HEDLEY_VERSION_DECODE_MAJOR -#endif -#define JSON_HEDLEY_VERSION_DECODE_MAJOR(version) ((version) / 1000000) - -#if defined(JSON_HEDLEY_VERSION_DECODE_MINOR) - #undef JSON_HEDLEY_VERSION_DECODE_MINOR -#endif -#define JSON_HEDLEY_VERSION_DECODE_MINOR(version) (((version) % 1000000) / 1000) - -#if defined(JSON_HEDLEY_VERSION_DECODE_REVISION) - #undef JSON_HEDLEY_VERSION_DECODE_REVISION -#endif -#define JSON_HEDLEY_VERSION_DECODE_REVISION(version) ((version) % 1000) - -#if defined(JSON_HEDLEY_GNUC_VERSION) - #undef JSON_HEDLEY_GNUC_VERSION -#endif -#if defined(__GNUC__) && defined(__GNUC_PATCHLEVEL__) - #define JSON_HEDLEY_GNUC_VERSION JSON_HEDLEY_VERSION_ENCODE(__GNUC__, __GNUC_MINOR__, __GNUC_PATCHLEVEL__) -#elif defined(__GNUC__) - #define JSON_HEDLEY_GNUC_VERSION JSON_HEDLEY_VERSION_ENCODE(__GNUC__, __GNUC_MINOR__, 0) -#endif - -#if defined(JSON_HEDLEY_GNUC_VERSION_CHECK) - #undef JSON_HEDLEY_GNUC_VERSION_CHECK -#endif -#if defined(JSON_HEDLEY_GNUC_VERSION) - #define JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_GNUC_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) -#else - #define JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) (0) -#endif - -#if defined(JSON_HEDLEY_MSVC_VERSION) - #undef JSON_HEDLEY_MSVC_VERSION -#endif -#if defined(_MSC_FULL_VER) && (_MSC_FULL_VER >= 140000000) && !defined(__ICL) - #define JSON_HEDLEY_MSVC_VERSION JSON_HEDLEY_VERSION_ENCODE(_MSC_FULL_VER / 10000000, (_MSC_FULL_VER % 10000000) / 100000, (_MSC_FULL_VER % 100000) / 100) -#elif defined(_MSC_FULL_VER) && !defined(__ICL) - #define JSON_HEDLEY_MSVC_VERSION JSON_HEDLEY_VERSION_ENCODE(_MSC_FULL_VER / 1000000, (_MSC_FULL_VER % 1000000) / 10000, (_MSC_FULL_VER % 10000) / 10) -#elif defined(_MSC_VER) && !defined(__ICL) - #define JSON_HEDLEY_MSVC_VERSION JSON_HEDLEY_VERSION_ENCODE(_MSC_VER / 100, _MSC_VER % 100, 0) -#endif - -#if defined(JSON_HEDLEY_MSVC_VERSION_CHECK) - #undef JSON_HEDLEY_MSVC_VERSION_CHECK -#endif -#if !defined(JSON_HEDLEY_MSVC_VERSION) - #define JSON_HEDLEY_MSVC_VERSION_CHECK(major,minor,patch) (0) -#elif defined(_MSC_VER) && (_MSC_VER >= 1400) - #define JSON_HEDLEY_MSVC_VERSION_CHECK(major,minor,patch) (_MSC_FULL_VER >= ((major * 10000000) + (minor * 100000) + (patch))) -#elif defined(_MSC_VER) && (_MSC_VER >= 1200) - #define JSON_HEDLEY_MSVC_VERSION_CHECK(major,minor,patch) (_MSC_FULL_VER >= ((major * 1000000) + (minor * 10000) + (patch))) -#else - #define JSON_HEDLEY_MSVC_VERSION_CHECK(major,minor,patch) (_MSC_VER >= ((major * 100) + (minor))) -#endif - -#if defined(JSON_HEDLEY_INTEL_VERSION) - #undef JSON_HEDLEY_INTEL_VERSION -#endif -#if defined(__INTEL_COMPILER) && defined(__INTEL_COMPILER_UPDATE) && !defined(__ICL) - #define JSON_HEDLEY_INTEL_VERSION JSON_HEDLEY_VERSION_ENCODE(__INTEL_COMPILER / 100, __INTEL_COMPILER % 100, __INTEL_COMPILER_UPDATE) -#elif defined(__INTEL_COMPILER) && !defined(__ICL) - #define JSON_HEDLEY_INTEL_VERSION JSON_HEDLEY_VERSION_ENCODE(__INTEL_COMPILER / 100, __INTEL_COMPILER % 100, 0) -#endif - -#if defined(JSON_HEDLEY_INTEL_VERSION_CHECK) - #undef JSON_HEDLEY_INTEL_VERSION_CHECK -#endif -#if defined(JSON_HEDLEY_INTEL_VERSION) - #define JSON_HEDLEY_INTEL_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_INTEL_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) -#else - #define JSON_HEDLEY_INTEL_VERSION_CHECK(major,minor,patch) (0) -#endif - -#if defined(JSON_HEDLEY_INTEL_CL_VERSION) - #undef JSON_HEDLEY_INTEL_CL_VERSION -#endif -#if defined(__INTEL_COMPILER) && defined(__INTEL_COMPILER_UPDATE) && defined(__ICL) - #define JSON_HEDLEY_INTEL_CL_VERSION JSON_HEDLEY_VERSION_ENCODE(__INTEL_COMPILER, __INTEL_COMPILER_UPDATE, 0) -#endif - -#if defined(JSON_HEDLEY_INTEL_CL_VERSION_CHECK) - #undef JSON_HEDLEY_INTEL_CL_VERSION_CHECK -#endif -#if defined(JSON_HEDLEY_INTEL_CL_VERSION) - #define JSON_HEDLEY_INTEL_CL_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_INTEL_CL_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) -#else - #define JSON_HEDLEY_INTEL_CL_VERSION_CHECK(major,minor,patch) (0) -#endif - -#if defined(JSON_HEDLEY_PGI_VERSION) - #undef JSON_HEDLEY_PGI_VERSION -#endif -#if defined(__PGI) && defined(__PGIC__) && defined(__PGIC_MINOR__) && defined(__PGIC_PATCHLEVEL__) - #define JSON_HEDLEY_PGI_VERSION JSON_HEDLEY_VERSION_ENCODE(__PGIC__, __PGIC_MINOR__, __PGIC_PATCHLEVEL__) -#endif - -#if defined(JSON_HEDLEY_PGI_VERSION_CHECK) - #undef JSON_HEDLEY_PGI_VERSION_CHECK -#endif -#if defined(JSON_HEDLEY_PGI_VERSION) - #define JSON_HEDLEY_PGI_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_PGI_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) -#else - #define JSON_HEDLEY_PGI_VERSION_CHECK(major,minor,patch) (0) -#endif - -#if defined(JSON_HEDLEY_SUNPRO_VERSION) - #undef JSON_HEDLEY_SUNPRO_VERSION -#endif -#if defined(__SUNPRO_C) && (__SUNPRO_C > 0x1000) - #define JSON_HEDLEY_SUNPRO_VERSION JSON_HEDLEY_VERSION_ENCODE((((__SUNPRO_C >> 16) & 0xf) * 10) + ((__SUNPRO_C >> 12) & 0xf), (((__SUNPRO_C >> 8) & 0xf) * 10) + ((__SUNPRO_C >> 4) & 0xf), (__SUNPRO_C & 0xf) * 10) -#elif defined(__SUNPRO_C) - #define JSON_HEDLEY_SUNPRO_VERSION JSON_HEDLEY_VERSION_ENCODE((__SUNPRO_C >> 8) & 0xf, (__SUNPRO_C >> 4) & 0xf, (__SUNPRO_C) & 0xf) -#elif defined(__SUNPRO_CC) && (__SUNPRO_CC > 0x1000) - #define JSON_HEDLEY_SUNPRO_VERSION JSON_HEDLEY_VERSION_ENCODE((((__SUNPRO_CC >> 16) & 0xf) * 10) + ((__SUNPRO_CC >> 12) & 0xf), (((__SUNPRO_CC >> 8) & 0xf) * 10) + ((__SUNPRO_CC >> 4) & 0xf), (__SUNPRO_CC & 0xf) * 10) -#elif defined(__SUNPRO_CC) - #define JSON_HEDLEY_SUNPRO_VERSION JSON_HEDLEY_VERSION_ENCODE((__SUNPRO_CC >> 8) & 0xf, (__SUNPRO_CC >> 4) & 0xf, (__SUNPRO_CC) & 0xf) -#endif - -#if defined(JSON_HEDLEY_SUNPRO_VERSION_CHECK) - #undef JSON_HEDLEY_SUNPRO_VERSION_CHECK -#endif -#if defined(JSON_HEDLEY_SUNPRO_VERSION) - #define JSON_HEDLEY_SUNPRO_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_SUNPRO_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) -#else - #define JSON_HEDLEY_SUNPRO_VERSION_CHECK(major,minor,patch) (0) -#endif - -#if defined(JSON_HEDLEY_EMSCRIPTEN_VERSION) - #undef JSON_HEDLEY_EMSCRIPTEN_VERSION -#endif -#if defined(__EMSCRIPTEN__) - #define JSON_HEDLEY_EMSCRIPTEN_VERSION JSON_HEDLEY_VERSION_ENCODE(__EMSCRIPTEN_major__, __EMSCRIPTEN_minor__, __EMSCRIPTEN_tiny__) -#endif - -#if defined(JSON_HEDLEY_EMSCRIPTEN_VERSION_CHECK) - #undef JSON_HEDLEY_EMSCRIPTEN_VERSION_CHECK -#endif -#if defined(JSON_HEDLEY_EMSCRIPTEN_VERSION) - #define JSON_HEDLEY_EMSCRIPTEN_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_EMSCRIPTEN_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) -#else - #define JSON_HEDLEY_EMSCRIPTEN_VERSION_CHECK(major,minor,patch) (0) -#endif - -#if defined(JSON_HEDLEY_ARM_VERSION) - #undef JSON_HEDLEY_ARM_VERSION -#endif -#if defined(__CC_ARM) && defined(__ARMCOMPILER_VERSION) - #define JSON_HEDLEY_ARM_VERSION JSON_HEDLEY_VERSION_ENCODE(__ARMCOMPILER_VERSION / 1000000, (__ARMCOMPILER_VERSION % 1000000) / 10000, (__ARMCOMPILER_VERSION % 10000) / 100) -#elif defined(__CC_ARM) && defined(__ARMCC_VERSION) - #define JSON_HEDLEY_ARM_VERSION JSON_HEDLEY_VERSION_ENCODE(__ARMCC_VERSION / 1000000, (__ARMCC_VERSION % 1000000) / 10000, (__ARMCC_VERSION % 10000) / 100) -#endif - -#if defined(JSON_HEDLEY_ARM_VERSION_CHECK) - #undef JSON_HEDLEY_ARM_VERSION_CHECK -#endif -#if defined(JSON_HEDLEY_ARM_VERSION) - #define JSON_HEDLEY_ARM_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_ARM_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) -#else - #define JSON_HEDLEY_ARM_VERSION_CHECK(major,minor,patch) (0) -#endif - -#if defined(JSON_HEDLEY_IBM_VERSION) - #undef JSON_HEDLEY_IBM_VERSION -#endif -#if defined(__ibmxl__) - #define JSON_HEDLEY_IBM_VERSION JSON_HEDLEY_VERSION_ENCODE(__ibmxl_version__, __ibmxl_release__, __ibmxl_modification__) -#elif defined(__xlC__) && defined(__xlC_ver__) - #define JSON_HEDLEY_IBM_VERSION JSON_HEDLEY_VERSION_ENCODE(__xlC__ >> 8, __xlC__ & 0xff, (__xlC_ver__ >> 8) & 0xff) -#elif defined(__xlC__) - #define JSON_HEDLEY_IBM_VERSION JSON_HEDLEY_VERSION_ENCODE(__xlC__ >> 8, __xlC__ & 0xff, 0) -#endif - -#if defined(JSON_HEDLEY_IBM_VERSION_CHECK) - #undef JSON_HEDLEY_IBM_VERSION_CHECK -#endif -#if defined(JSON_HEDLEY_IBM_VERSION) - #define JSON_HEDLEY_IBM_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_IBM_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) -#else - #define JSON_HEDLEY_IBM_VERSION_CHECK(major,minor,patch) (0) -#endif - -#if defined(JSON_HEDLEY_TI_VERSION) - #undef JSON_HEDLEY_TI_VERSION -#endif -#if \ - defined(__TI_COMPILER_VERSION__) && \ - ( \ - defined(__TMS470__) || defined(__TI_ARM__) || \ - defined(__MSP430__) || \ - defined(__TMS320C2000__) \ - ) -#if (__TI_COMPILER_VERSION__ >= 16000000) - #define JSON_HEDLEY_TI_VERSION JSON_HEDLEY_VERSION_ENCODE(__TI_COMPILER_VERSION__ / 1000000, (__TI_COMPILER_VERSION__ % 1000000) / 1000, (__TI_COMPILER_VERSION__ % 1000)) -#endif -#endif - -#if defined(JSON_HEDLEY_TI_VERSION_CHECK) - #undef JSON_HEDLEY_TI_VERSION_CHECK -#endif -#if defined(JSON_HEDLEY_TI_VERSION) - #define JSON_HEDLEY_TI_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_TI_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) -#else - #define JSON_HEDLEY_TI_VERSION_CHECK(major,minor,patch) (0) -#endif - -#if defined(JSON_HEDLEY_TI_CL2000_VERSION) - #undef JSON_HEDLEY_TI_CL2000_VERSION -#endif -#if defined(__TI_COMPILER_VERSION__) && defined(__TMS320C2000__) - #define JSON_HEDLEY_TI_CL2000_VERSION JSON_HEDLEY_VERSION_ENCODE(__TI_COMPILER_VERSION__ / 1000000, (__TI_COMPILER_VERSION__ % 1000000) / 1000, (__TI_COMPILER_VERSION__ % 1000)) -#endif - -#if defined(JSON_HEDLEY_TI_CL2000_VERSION_CHECK) - #undef JSON_HEDLEY_TI_CL2000_VERSION_CHECK -#endif -#if defined(JSON_HEDLEY_TI_CL2000_VERSION) - #define JSON_HEDLEY_TI_CL2000_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_TI_CL2000_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) -#else - #define JSON_HEDLEY_TI_CL2000_VERSION_CHECK(major,minor,patch) (0) -#endif - -#if defined(JSON_HEDLEY_TI_CL430_VERSION) - #undef JSON_HEDLEY_TI_CL430_VERSION -#endif -#if defined(__TI_COMPILER_VERSION__) && defined(__MSP430__) - #define JSON_HEDLEY_TI_CL430_VERSION JSON_HEDLEY_VERSION_ENCODE(__TI_COMPILER_VERSION__ / 1000000, (__TI_COMPILER_VERSION__ % 1000000) / 1000, (__TI_COMPILER_VERSION__ % 1000)) -#endif - -#if defined(JSON_HEDLEY_TI_CL430_VERSION_CHECK) - #undef JSON_HEDLEY_TI_CL430_VERSION_CHECK -#endif -#if defined(JSON_HEDLEY_TI_CL430_VERSION) - #define JSON_HEDLEY_TI_CL430_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_TI_CL430_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) -#else - #define JSON_HEDLEY_TI_CL430_VERSION_CHECK(major,minor,patch) (0) -#endif - -#if defined(JSON_HEDLEY_TI_ARMCL_VERSION) - #undef JSON_HEDLEY_TI_ARMCL_VERSION -#endif -#if defined(__TI_COMPILER_VERSION__) && (defined(__TMS470__) || defined(__TI_ARM__)) - #define JSON_HEDLEY_TI_ARMCL_VERSION JSON_HEDLEY_VERSION_ENCODE(__TI_COMPILER_VERSION__ / 1000000, (__TI_COMPILER_VERSION__ % 1000000) / 1000, (__TI_COMPILER_VERSION__ % 1000)) -#endif - -#if defined(JSON_HEDLEY_TI_ARMCL_VERSION_CHECK) - #undef JSON_HEDLEY_TI_ARMCL_VERSION_CHECK -#endif -#if defined(JSON_HEDLEY_TI_ARMCL_VERSION) - #define JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_TI_ARMCL_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) -#else - #define JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(major,minor,patch) (0) -#endif - -#if defined(JSON_HEDLEY_TI_CL6X_VERSION) - #undef JSON_HEDLEY_TI_CL6X_VERSION -#endif -#if defined(__TI_COMPILER_VERSION__) && defined(__TMS320C6X__) - #define JSON_HEDLEY_TI_CL6X_VERSION JSON_HEDLEY_VERSION_ENCODE(__TI_COMPILER_VERSION__ / 1000000, (__TI_COMPILER_VERSION__ % 1000000) / 1000, (__TI_COMPILER_VERSION__ % 1000)) -#endif - -#if defined(JSON_HEDLEY_TI_CL6X_VERSION_CHECK) - #undef JSON_HEDLEY_TI_CL6X_VERSION_CHECK -#endif -#if defined(JSON_HEDLEY_TI_CL6X_VERSION) - #define JSON_HEDLEY_TI_CL6X_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_TI_CL6X_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) -#else - #define JSON_HEDLEY_TI_CL6X_VERSION_CHECK(major,minor,patch) (0) -#endif - -#if defined(JSON_HEDLEY_TI_CL7X_VERSION) - #undef JSON_HEDLEY_TI_CL7X_VERSION -#endif -#if defined(__TI_COMPILER_VERSION__) && defined(__C7000__) - #define JSON_HEDLEY_TI_CL7X_VERSION JSON_HEDLEY_VERSION_ENCODE(__TI_COMPILER_VERSION__ / 1000000, (__TI_COMPILER_VERSION__ % 1000000) / 1000, (__TI_COMPILER_VERSION__ % 1000)) -#endif - -#if defined(JSON_HEDLEY_TI_CL7X_VERSION_CHECK) - #undef JSON_HEDLEY_TI_CL7X_VERSION_CHECK -#endif -#if defined(JSON_HEDLEY_TI_CL7X_VERSION) - #define JSON_HEDLEY_TI_CL7X_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_TI_CL7X_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) -#else - #define JSON_HEDLEY_TI_CL7X_VERSION_CHECK(major,minor,patch) (0) -#endif - -#if defined(JSON_HEDLEY_TI_CLPRU_VERSION) - #undef JSON_HEDLEY_TI_CLPRU_VERSION -#endif -#if defined(__TI_COMPILER_VERSION__) && defined(__PRU__) - #define JSON_HEDLEY_TI_CLPRU_VERSION JSON_HEDLEY_VERSION_ENCODE(__TI_COMPILER_VERSION__ / 1000000, (__TI_COMPILER_VERSION__ % 1000000) / 1000, (__TI_COMPILER_VERSION__ % 1000)) -#endif - -#if defined(JSON_HEDLEY_TI_CLPRU_VERSION_CHECK) - #undef JSON_HEDLEY_TI_CLPRU_VERSION_CHECK -#endif -#if defined(JSON_HEDLEY_TI_CLPRU_VERSION) - #define JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_TI_CLPRU_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) -#else - #define JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(major,minor,patch) (0) -#endif - -#if defined(JSON_HEDLEY_CRAY_VERSION) - #undef JSON_HEDLEY_CRAY_VERSION -#endif -#if defined(_CRAYC) - #if defined(_RELEASE_PATCHLEVEL) - #define JSON_HEDLEY_CRAY_VERSION JSON_HEDLEY_VERSION_ENCODE(_RELEASE_MAJOR, _RELEASE_MINOR, _RELEASE_PATCHLEVEL) - #else - #define JSON_HEDLEY_CRAY_VERSION JSON_HEDLEY_VERSION_ENCODE(_RELEASE_MAJOR, _RELEASE_MINOR, 0) - #endif -#endif - -#if defined(JSON_HEDLEY_CRAY_VERSION_CHECK) - #undef JSON_HEDLEY_CRAY_VERSION_CHECK -#endif -#if defined(JSON_HEDLEY_CRAY_VERSION) - #define JSON_HEDLEY_CRAY_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_CRAY_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) -#else - #define JSON_HEDLEY_CRAY_VERSION_CHECK(major,minor,patch) (0) -#endif - -#if defined(JSON_HEDLEY_IAR_VERSION) - #undef JSON_HEDLEY_IAR_VERSION -#endif -#if defined(__IAR_SYSTEMS_ICC__) - #if __VER__ > 1000 - #define JSON_HEDLEY_IAR_VERSION JSON_HEDLEY_VERSION_ENCODE((__VER__ / 1000000), ((__VER__ / 1000) % 1000), (__VER__ % 1000)) - #else - #define JSON_HEDLEY_IAR_VERSION JSON_HEDLEY_VERSION_ENCODE(__VER__ / 100, __VER__ % 100, 0) - #endif -#endif - -#if defined(JSON_HEDLEY_IAR_VERSION_CHECK) - #undef JSON_HEDLEY_IAR_VERSION_CHECK -#endif -#if defined(JSON_HEDLEY_IAR_VERSION) - #define JSON_HEDLEY_IAR_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_IAR_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) -#else - #define JSON_HEDLEY_IAR_VERSION_CHECK(major,minor,patch) (0) -#endif - -#if defined(JSON_HEDLEY_TINYC_VERSION) - #undef JSON_HEDLEY_TINYC_VERSION -#endif -#if defined(__TINYC__) - #define JSON_HEDLEY_TINYC_VERSION JSON_HEDLEY_VERSION_ENCODE(__TINYC__ / 1000, (__TINYC__ / 100) % 10, __TINYC__ % 100) -#endif - -#if defined(JSON_HEDLEY_TINYC_VERSION_CHECK) - #undef JSON_HEDLEY_TINYC_VERSION_CHECK -#endif -#if defined(JSON_HEDLEY_TINYC_VERSION) - #define JSON_HEDLEY_TINYC_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_TINYC_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) -#else - #define JSON_HEDLEY_TINYC_VERSION_CHECK(major,minor,patch) (0) -#endif - -#if defined(JSON_HEDLEY_DMC_VERSION) - #undef JSON_HEDLEY_DMC_VERSION -#endif -#if defined(__DMC__) - #define JSON_HEDLEY_DMC_VERSION JSON_HEDLEY_VERSION_ENCODE(__DMC__ >> 8, (__DMC__ >> 4) & 0xf, __DMC__ & 0xf) -#endif - -#if defined(JSON_HEDLEY_DMC_VERSION_CHECK) - #undef JSON_HEDLEY_DMC_VERSION_CHECK -#endif -#if defined(JSON_HEDLEY_DMC_VERSION) - #define JSON_HEDLEY_DMC_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_DMC_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) -#else - #define JSON_HEDLEY_DMC_VERSION_CHECK(major,minor,patch) (0) -#endif - -#if defined(JSON_HEDLEY_COMPCERT_VERSION) - #undef JSON_HEDLEY_COMPCERT_VERSION -#endif -#if defined(__COMPCERT_VERSION__) - #define JSON_HEDLEY_COMPCERT_VERSION JSON_HEDLEY_VERSION_ENCODE(__COMPCERT_VERSION__ / 10000, (__COMPCERT_VERSION__ / 100) % 100, __COMPCERT_VERSION__ % 100) -#endif - -#if defined(JSON_HEDLEY_COMPCERT_VERSION_CHECK) - #undef JSON_HEDLEY_COMPCERT_VERSION_CHECK -#endif -#if defined(JSON_HEDLEY_COMPCERT_VERSION) - #define JSON_HEDLEY_COMPCERT_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_COMPCERT_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) -#else - #define JSON_HEDLEY_COMPCERT_VERSION_CHECK(major,minor,patch) (0) -#endif - -#if defined(JSON_HEDLEY_PELLES_VERSION) - #undef JSON_HEDLEY_PELLES_VERSION -#endif -#if defined(__POCC__) - #define JSON_HEDLEY_PELLES_VERSION JSON_HEDLEY_VERSION_ENCODE(__POCC__ / 100, __POCC__ % 100, 0) -#endif - -#if defined(JSON_HEDLEY_PELLES_VERSION_CHECK) - #undef JSON_HEDLEY_PELLES_VERSION_CHECK -#endif -#if defined(JSON_HEDLEY_PELLES_VERSION) - #define JSON_HEDLEY_PELLES_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_PELLES_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) -#else - #define JSON_HEDLEY_PELLES_VERSION_CHECK(major,minor,patch) (0) -#endif - -#if defined(JSON_HEDLEY_MCST_LCC_VERSION) - #undef JSON_HEDLEY_MCST_LCC_VERSION -#endif -#if defined(__LCC__) && defined(__LCC_MINOR__) - #define JSON_HEDLEY_MCST_LCC_VERSION JSON_HEDLEY_VERSION_ENCODE(__LCC__ / 100, __LCC__ % 100, __LCC_MINOR__) -#endif - -#if defined(JSON_HEDLEY_MCST_LCC_VERSION_CHECK) - #undef JSON_HEDLEY_MCST_LCC_VERSION_CHECK -#endif -#if defined(JSON_HEDLEY_MCST_LCC_VERSION) - #define JSON_HEDLEY_MCST_LCC_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_MCST_LCC_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) -#else - #define JSON_HEDLEY_MCST_LCC_VERSION_CHECK(major,minor,patch) (0) -#endif - -#if defined(JSON_HEDLEY_GCC_VERSION) - #undef JSON_HEDLEY_GCC_VERSION -#endif -#if \ - defined(JSON_HEDLEY_GNUC_VERSION) && \ - !defined(__clang__) && \ - !defined(JSON_HEDLEY_INTEL_VERSION) && \ - !defined(JSON_HEDLEY_PGI_VERSION) && \ - !defined(JSON_HEDLEY_ARM_VERSION) && \ - !defined(JSON_HEDLEY_CRAY_VERSION) && \ - !defined(JSON_HEDLEY_TI_VERSION) && \ - !defined(JSON_HEDLEY_TI_ARMCL_VERSION) && \ - !defined(JSON_HEDLEY_TI_CL430_VERSION) && \ - !defined(JSON_HEDLEY_TI_CL2000_VERSION) && \ - !defined(JSON_HEDLEY_TI_CL6X_VERSION) && \ - !defined(JSON_HEDLEY_TI_CL7X_VERSION) && \ - !defined(JSON_HEDLEY_TI_CLPRU_VERSION) && \ - !defined(__COMPCERT__) && \ - !defined(JSON_HEDLEY_MCST_LCC_VERSION) - #define JSON_HEDLEY_GCC_VERSION JSON_HEDLEY_GNUC_VERSION -#endif - -#if defined(JSON_HEDLEY_GCC_VERSION_CHECK) - #undef JSON_HEDLEY_GCC_VERSION_CHECK -#endif -#if defined(JSON_HEDLEY_GCC_VERSION) - #define JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_GCC_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) -#else - #define JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) (0) -#endif - -#if defined(JSON_HEDLEY_HAS_ATTRIBUTE) - #undef JSON_HEDLEY_HAS_ATTRIBUTE -#endif -#if \ - defined(__has_attribute) && \ - ( \ - (!defined(JSON_HEDLEY_IAR_VERSION) || JSON_HEDLEY_IAR_VERSION_CHECK(8,5,9)) \ - ) -# define JSON_HEDLEY_HAS_ATTRIBUTE(attribute) __has_attribute(attribute) -#else -# define JSON_HEDLEY_HAS_ATTRIBUTE(attribute) (0) -#endif - -#if defined(JSON_HEDLEY_GNUC_HAS_ATTRIBUTE) - #undef JSON_HEDLEY_GNUC_HAS_ATTRIBUTE -#endif -#if defined(__has_attribute) - #define JSON_HEDLEY_GNUC_HAS_ATTRIBUTE(attribute,major,minor,patch) JSON_HEDLEY_HAS_ATTRIBUTE(attribute) -#else - #define JSON_HEDLEY_GNUC_HAS_ATTRIBUTE(attribute,major,minor,patch) JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) -#endif - -#if defined(JSON_HEDLEY_GCC_HAS_ATTRIBUTE) - #undef JSON_HEDLEY_GCC_HAS_ATTRIBUTE -#endif -#if defined(__has_attribute) - #define JSON_HEDLEY_GCC_HAS_ATTRIBUTE(attribute,major,minor,patch) JSON_HEDLEY_HAS_ATTRIBUTE(attribute) -#else - #define JSON_HEDLEY_GCC_HAS_ATTRIBUTE(attribute,major,minor,patch) JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) -#endif - -#if defined(JSON_HEDLEY_HAS_CPP_ATTRIBUTE) - #undef JSON_HEDLEY_HAS_CPP_ATTRIBUTE -#endif -#if \ - defined(__has_cpp_attribute) && \ - defined(__cplusplus) && \ - (!defined(JSON_HEDLEY_SUNPRO_VERSION) || JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,15,0)) - #define JSON_HEDLEY_HAS_CPP_ATTRIBUTE(attribute) __has_cpp_attribute(attribute) -#else - #define JSON_HEDLEY_HAS_CPP_ATTRIBUTE(attribute) (0) -#endif - -#if defined(JSON_HEDLEY_HAS_CPP_ATTRIBUTE_NS) - #undef JSON_HEDLEY_HAS_CPP_ATTRIBUTE_NS -#endif -#if !defined(__cplusplus) || !defined(__has_cpp_attribute) - #define JSON_HEDLEY_HAS_CPP_ATTRIBUTE_NS(ns,attribute) (0) -#elif \ - !defined(JSON_HEDLEY_PGI_VERSION) && \ - !defined(JSON_HEDLEY_IAR_VERSION) && \ - (!defined(JSON_HEDLEY_SUNPRO_VERSION) || JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,15,0)) && \ - (!defined(JSON_HEDLEY_MSVC_VERSION) || JSON_HEDLEY_MSVC_VERSION_CHECK(19,20,0)) - #define JSON_HEDLEY_HAS_CPP_ATTRIBUTE_NS(ns,attribute) JSON_HEDLEY_HAS_CPP_ATTRIBUTE(ns::attribute) -#else - #define JSON_HEDLEY_HAS_CPP_ATTRIBUTE_NS(ns,attribute) (0) -#endif - -#if defined(JSON_HEDLEY_GNUC_HAS_CPP_ATTRIBUTE) - #undef JSON_HEDLEY_GNUC_HAS_CPP_ATTRIBUTE -#endif -#if defined(__has_cpp_attribute) && defined(__cplusplus) - #define JSON_HEDLEY_GNUC_HAS_CPP_ATTRIBUTE(attribute,major,minor,patch) __has_cpp_attribute(attribute) -#else - #define JSON_HEDLEY_GNUC_HAS_CPP_ATTRIBUTE(attribute,major,minor,patch) JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) -#endif - -#if defined(JSON_HEDLEY_GCC_HAS_CPP_ATTRIBUTE) - #undef JSON_HEDLEY_GCC_HAS_CPP_ATTRIBUTE -#endif -#if defined(__has_cpp_attribute) && defined(__cplusplus) - #define JSON_HEDLEY_GCC_HAS_CPP_ATTRIBUTE(attribute,major,minor,patch) __has_cpp_attribute(attribute) -#else - #define JSON_HEDLEY_GCC_HAS_CPP_ATTRIBUTE(attribute,major,minor,patch) JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) -#endif - -#if defined(JSON_HEDLEY_HAS_BUILTIN) - #undef JSON_HEDLEY_HAS_BUILTIN -#endif -#if defined(__has_builtin) - #define JSON_HEDLEY_HAS_BUILTIN(builtin) __has_builtin(builtin) -#else - #define JSON_HEDLEY_HAS_BUILTIN(builtin) (0) -#endif - -#if defined(JSON_HEDLEY_GNUC_HAS_BUILTIN) - #undef JSON_HEDLEY_GNUC_HAS_BUILTIN -#endif -#if defined(__has_builtin) - #define JSON_HEDLEY_GNUC_HAS_BUILTIN(builtin,major,minor,patch) __has_builtin(builtin) -#else - #define JSON_HEDLEY_GNUC_HAS_BUILTIN(builtin,major,minor,patch) JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) -#endif - -#if defined(JSON_HEDLEY_GCC_HAS_BUILTIN) - #undef JSON_HEDLEY_GCC_HAS_BUILTIN -#endif -#if defined(__has_builtin) - #define JSON_HEDLEY_GCC_HAS_BUILTIN(builtin,major,minor,patch) __has_builtin(builtin) -#else - #define JSON_HEDLEY_GCC_HAS_BUILTIN(builtin,major,minor,patch) JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) -#endif - -#if defined(JSON_HEDLEY_HAS_FEATURE) - #undef JSON_HEDLEY_HAS_FEATURE -#endif -#if defined(__has_feature) - #define JSON_HEDLEY_HAS_FEATURE(feature) __has_feature(feature) -#else - #define JSON_HEDLEY_HAS_FEATURE(feature) (0) -#endif - -#if defined(JSON_HEDLEY_GNUC_HAS_FEATURE) - #undef JSON_HEDLEY_GNUC_HAS_FEATURE -#endif -#if defined(__has_feature) - #define JSON_HEDLEY_GNUC_HAS_FEATURE(feature,major,minor,patch) __has_feature(feature) -#else - #define JSON_HEDLEY_GNUC_HAS_FEATURE(feature,major,minor,patch) JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) -#endif - -#if defined(JSON_HEDLEY_GCC_HAS_FEATURE) - #undef JSON_HEDLEY_GCC_HAS_FEATURE -#endif -#if defined(__has_feature) - #define JSON_HEDLEY_GCC_HAS_FEATURE(feature,major,minor,patch) __has_feature(feature) -#else - #define JSON_HEDLEY_GCC_HAS_FEATURE(feature,major,minor,patch) JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) -#endif - -#if defined(JSON_HEDLEY_HAS_EXTENSION) - #undef JSON_HEDLEY_HAS_EXTENSION -#endif -#if defined(__has_extension) - #define JSON_HEDLEY_HAS_EXTENSION(extension) __has_extension(extension) -#else - #define JSON_HEDLEY_HAS_EXTENSION(extension) (0) -#endif - -#if defined(JSON_HEDLEY_GNUC_HAS_EXTENSION) - #undef JSON_HEDLEY_GNUC_HAS_EXTENSION -#endif -#if defined(__has_extension) - #define JSON_HEDLEY_GNUC_HAS_EXTENSION(extension,major,minor,patch) __has_extension(extension) -#else - #define JSON_HEDLEY_GNUC_HAS_EXTENSION(extension,major,minor,patch) JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) -#endif - -#if defined(JSON_HEDLEY_GCC_HAS_EXTENSION) - #undef JSON_HEDLEY_GCC_HAS_EXTENSION -#endif -#if defined(__has_extension) - #define JSON_HEDLEY_GCC_HAS_EXTENSION(extension,major,minor,patch) __has_extension(extension) -#else - #define JSON_HEDLEY_GCC_HAS_EXTENSION(extension,major,minor,patch) JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) -#endif - -#if defined(JSON_HEDLEY_HAS_DECLSPEC_ATTRIBUTE) - #undef JSON_HEDLEY_HAS_DECLSPEC_ATTRIBUTE -#endif -#if defined(__has_declspec_attribute) - #define JSON_HEDLEY_HAS_DECLSPEC_ATTRIBUTE(attribute) __has_declspec_attribute(attribute) -#else - #define JSON_HEDLEY_HAS_DECLSPEC_ATTRIBUTE(attribute) (0) -#endif - -#if defined(JSON_HEDLEY_GNUC_HAS_DECLSPEC_ATTRIBUTE) - #undef JSON_HEDLEY_GNUC_HAS_DECLSPEC_ATTRIBUTE -#endif -#if defined(__has_declspec_attribute) - #define JSON_HEDLEY_GNUC_HAS_DECLSPEC_ATTRIBUTE(attribute,major,minor,patch) __has_declspec_attribute(attribute) -#else - #define JSON_HEDLEY_GNUC_HAS_DECLSPEC_ATTRIBUTE(attribute,major,minor,patch) JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) -#endif - -#if defined(JSON_HEDLEY_GCC_HAS_DECLSPEC_ATTRIBUTE) - #undef JSON_HEDLEY_GCC_HAS_DECLSPEC_ATTRIBUTE -#endif -#if defined(__has_declspec_attribute) - #define JSON_HEDLEY_GCC_HAS_DECLSPEC_ATTRIBUTE(attribute,major,minor,patch) __has_declspec_attribute(attribute) -#else - #define JSON_HEDLEY_GCC_HAS_DECLSPEC_ATTRIBUTE(attribute,major,minor,patch) JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) -#endif - -#if defined(JSON_HEDLEY_HAS_WARNING) - #undef JSON_HEDLEY_HAS_WARNING -#endif -#if defined(__has_warning) - #define JSON_HEDLEY_HAS_WARNING(warning) __has_warning(warning) -#else - #define JSON_HEDLEY_HAS_WARNING(warning) (0) -#endif - -#if defined(JSON_HEDLEY_GNUC_HAS_WARNING) - #undef JSON_HEDLEY_GNUC_HAS_WARNING -#endif -#if defined(__has_warning) - #define JSON_HEDLEY_GNUC_HAS_WARNING(warning,major,minor,patch) __has_warning(warning) -#else - #define JSON_HEDLEY_GNUC_HAS_WARNING(warning,major,minor,patch) JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) -#endif - -#if defined(JSON_HEDLEY_GCC_HAS_WARNING) - #undef JSON_HEDLEY_GCC_HAS_WARNING -#endif -#if defined(__has_warning) - #define JSON_HEDLEY_GCC_HAS_WARNING(warning,major,minor,patch) __has_warning(warning) -#else - #define JSON_HEDLEY_GCC_HAS_WARNING(warning,major,minor,patch) JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) -#endif - -#if \ - (defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L)) || \ - defined(__clang__) || \ - JSON_HEDLEY_GCC_VERSION_CHECK(3,0,0) || \ - JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ - JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) || \ - JSON_HEDLEY_PGI_VERSION_CHECK(18,4,0) || \ - JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ - JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ - JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,7,0) || \ - JSON_HEDLEY_TI_CL430_VERSION_CHECK(2,0,1) || \ - JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,1,0) || \ - JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,0,0) || \ - JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ - JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ - JSON_HEDLEY_CRAY_VERSION_CHECK(5,0,0) || \ - JSON_HEDLEY_TINYC_VERSION_CHECK(0,9,17) || \ - JSON_HEDLEY_SUNPRO_VERSION_CHECK(8,0,0) || \ - (JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) && defined(__C99_PRAGMA_OPERATOR)) - #define JSON_HEDLEY_PRAGMA(value) _Pragma(#value) -#elif JSON_HEDLEY_MSVC_VERSION_CHECK(15,0,0) - #define JSON_HEDLEY_PRAGMA(value) __pragma(value) -#else - #define JSON_HEDLEY_PRAGMA(value) -#endif - -#if defined(JSON_HEDLEY_DIAGNOSTIC_PUSH) - #undef JSON_HEDLEY_DIAGNOSTIC_PUSH -#endif -#if defined(JSON_HEDLEY_DIAGNOSTIC_POP) - #undef JSON_HEDLEY_DIAGNOSTIC_POP -#endif -#if defined(__clang__) - #define JSON_HEDLEY_DIAGNOSTIC_PUSH _Pragma("clang diagnostic push") - #define JSON_HEDLEY_DIAGNOSTIC_POP _Pragma("clang diagnostic pop") -#elif JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) - #define JSON_HEDLEY_DIAGNOSTIC_PUSH _Pragma("warning(push)") - #define JSON_HEDLEY_DIAGNOSTIC_POP _Pragma("warning(pop)") -#elif JSON_HEDLEY_GCC_VERSION_CHECK(4,6,0) - #define JSON_HEDLEY_DIAGNOSTIC_PUSH _Pragma("GCC diagnostic push") - #define JSON_HEDLEY_DIAGNOSTIC_POP _Pragma("GCC diagnostic pop") -#elif \ - JSON_HEDLEY_MSVC_VERSION_CHECK(15,0,0) || \ - JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) - #define JSON_HEDLEY_DIAGNOSTIC_PUSH __pragma(warning(push)) - #define JSON_HEDLEY_DIAGNOSTIC_POP __pragma(warning(pop)) -#elif JSON_HEDLEY_ARM_VERSION_CHECK(5,6,0) - #define JSON_HEDLEY_DIAGNOSTIC_PUSH _Pragma("push") - #define JSON_HEDLEY_DIAGNOSTIC_POP _Pragma("pop") -#elif \ - JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ - JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ - JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,4,0) || \ - JSON_HEDLEY_TI_CL6X_VERSION_CHECK(8,1,0) || \ - JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ - JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) - #define JSON_HEDLEY_DIAGNOSTIC_PUSH _Pragma("diag_push") - #define JSON_HEDLEY_DIAGNOSTIC_POP _Pragma("diag_pop") -#elif JSON_HEDLEY_PELLES_VERSION_CHECK(2,90,0) - #define JSON_HEDLEY_DIAGNOSTIC_PUSH _Pragma("warning(push)") - #define JSON_HEDLEY_DIAGNOSTIC_POP _Pragma("warning(pop)") -#else - #define JSON_HEDLEY_DIAGNOSTIC_PUSH - #define JSON_HEDLEY_DIAGNOSTIC_POP -#endif - -/* JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_ is for - HEDLEY INTERNAL USE ONLY. API subject to change without notice. */ -#if defined(JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_) - #undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_ -#endif -#if defined(__cplusplus) -# if JSON_HEDLEY_HAS_WARNING("-Wc++98-compat") -# if JSON_HEDLEY_HAS_WARNING("-Wc++17-extensions") -# if JSON_HEDLEY_HAS_WARNING("-Wc++1z-extensions") -# define JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_(xpr) \ - JSON_HEDLEY_DIAGNOSTIC_PUSH \ - _Pragma("clang diagnostic ignored \"-Wc++98-compat\"") \ - _Pragma("clang diagnostic ignored \"-Wc++17-extensions\"") \ - _Pragma("clang diagnostic ignored \"-Wc++1z-extensions\"") \ - xpr \ - JSON_HEDLEY_DIAGNOSTIC_POP -# else -# define JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_(xpr) \ - JSON_HEDLEY_DIAGNOSTIC_PUSH \ - _Pragma("clang diagnostic ignored \"-Wc++98-compat\"") \ - _Pragma("clang diagnostic ignored \"-Wc++17-extensions\"") \ - xpr \ - JSON_HEDLEY_DIAGNOSTIC_POP -# endif -# else -# define JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_(xpr) \ - JSON_HEDLEY_DIAGNOSTIC_PUSH \ - _Pragma("clang diagnostic ignored \"-Wc++98-compat\"") \ - xpr \ - JSON_HEDLEY_DIAGNOSTIC_POP -# endif -# endif -#endif -#if !defined(JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_) - #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_(x) x -#endif - -#if defined(JSON_HEDLEY_CONST_CAST) - #undef JSON_HEDLEY_CONST_CAST -#endif -#if defined(__cplusplus) -# define JSON_HEDLEY_CONST_CAST(T, expr) (const_cast(expr)) -#elif \ - JSON_HEDLEY_HAS_WARNING("-Wcast-qual") || \ - JSON_HEDLEY_GCC_VERSION_CHECK(4,6,0) || \ - JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) -# define JSON_HEDLEY_CONST_CAST(T, expr) (__extension__ ({ \ - JSON_HEDLEY_DIAGNOSTIC_PUSH \ - JSON_HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL \ - ((T) (expr)); \ - JSON_HEDLEY_DIAGNOSTIC_POP \ - })) -#else -# define JSON_HEDLEY_CONST_CAST(T, expr) ((T) (expr)) -#endif - -#if defined(JSON_HEDLEY_REINTERPRET_CAST) - #undef JSON_HEDLEY_REINTERPRET_CAST -#endif -#if defined(__cplusplus) - #define JSON_HEDLEY_REINTERPRET_CAST(T, expr) (reinterpret_cast(expr)) -#else - #define JSON_HEDLEY_REINTERPRET_CAST(T, expr) ((T) (expr)) -#endif - -#if defined(JSON_HEDLEY_STATIC_CAST) - #undef JSON_HEDLEY_STATIC_CAST -#endif -#if defined(__cplusplus) - #define JSON_HEDLEY_STATIC_CAST(T, expr) (static_cast(expr)) -#else - #define JSON_HEDLEY_STATIC_CAST(T, expr) ((T) (expr)) -#endif - -#if defined(JSON_HEDLEY_CPP_CAST) - #undef JSON_HEDLEY_CPP_CAST -#endif -#if defined(__cplusplus) -# if JSON_HEDLEY_HAS_WARNING("-Wold-style-cast") -# define JSON_HEDLEY_CPP_CAST(T, expr) \ - JSON_HEDLEY_DIAGNOSTIC_PUSH \ - _Pragma("clang diagnostic ignored \"-Wold-style-cast\"") \ - ((T) (expr)) \ - JSON_HEDLEY_DIAGNOSTIC_POP -# elif JSON_HEDLEY_IAR_VERSION_CHECK(8,3,0) -# define JSON_HEDLEY_CPP_CAST(T, expr) \ - JSON_HEDLEY_DIAGNOSTIC_PUSH \ - _Pragma("diag_suppress=Pe137") \ - JSON_HEDLEY_DIAGNOSTIC_POP -# else -# define JSON_HEDLEY_CPP_CAST(T, expr) ((T) (expr)) -# endif -#else -# define JSON_HEDLEY_CPP_CAST(T, expr) (expr) -#endif - -#if defined(JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED) - #undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED -#endif -#if JSON_HEDLEY_HAS_WARNING("-Wdeprecated-declarations") - #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("clang diagnostic ignored \"-Wdeprecated-declarations\"") -#elif JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) - #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("warning(disable:1478 1786)") -#elif JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) - #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED __pragma(warning(disable:1478 1786)) -#elif JSON_HEDLEY_PGI_VERSION_CHECK(20,7,0) - #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("diag_suppress 1215,1216,1444,1445") -#elif JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0) - #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("diag_suppress 1215,1444") -#elif JSON_HEDLEY_GCC_VERSION_CHECK(4,3,0) - #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("GCC diagnostic ignored \"-Wdeprecated-declarations\"") -#elif JSON_HEDLEY_MSVC_VERSION_CHECK(15,0,0) - #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED __pragma(warning(disable:4996)) -#elif JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) - #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("diag_suppress 1215,1444") -#elif \ - JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ - (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ - JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ - (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ - JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ - (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ - JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ - (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ - JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ - JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ - JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) - #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("diag_suppress 1291,1718") -#elif JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,13,0) && !defined(__cplusplus) - #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("error_messages(off,E_DEPRECATED_ATT,E_DEPRECATED_ATT_MESS)") -#elif JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,13,0) && defined(__cplusplus) - #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("error_messages(off,symdeprecated,symdeprecated2)") -#elif JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) - #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("diag_suppress=Pe1444,Pe1215") -#elif JSON_HEDLEY_PELLES_VERSION_CHECK(2,90,0) - #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("warn(disable:2241)") -#else - #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED -#endif - -#if defined(JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS) - #undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS -#endif -#if JSON_HEDLEY_HAS_WARNING("-Wunknown-pragmas") - #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma("clang diagnostic ignored \"-Wunknown-pragmas\"") -#elif JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) - #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma("warning(disable:161)") -#elif JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) - #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS __pragma(warning(disable:161)) -#elif JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0) - #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma("diag_suppress 1675") -#elif JSON_HEDLEY_GCC_VERSION_CHECK(4,3,0) - #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma("GCC diagnostic ignored \"-Wunknown-pragmas\"") -#elif JSON_HEDLEY_MSVC_VERSION_CHECK(15,0,0) - #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS __pragma(warning(disable:4068)) -#elif \ - JSON_HEDLEY_TI_VERSION_CHECK(16,9,0) || \ - JSON_HEDLEY_TI_CL6X_VERSION_CHECK(8,0,0) || \ - JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ - JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,3,0) - #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma("diag_suppress 163") -#elif JSON_HEDLEY_TI_CL6X_VERSION_CHECK(8,0,0) - #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma("diag_suppress 163") -#elif JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) - #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma("diag_suppress=Pe161") -#elif JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) - #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma("diag_suppress 161") -#else - #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS -#endif - -#if defined(JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES) - #undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES -#endif -#if JSON_HEDLEY_HAS_WARNING("-Wunknown-attributes") - #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma("clang diagnostic ignored \"-Wunknown-attributes\"") -#elif JSON_HEDLEY_GCC_VERSION_CHECK(4,6,0) - #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma("GCC diagnostic ignored \"-Wdeprecated-declarations\"") -#elif JSON_HEDLEY_INTEL_VERSION_CHECK(17,0,0) - #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma("warning(disable:1292)") -#elif JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) - #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES __pragma(warning(disable:1292)) -#elif JSON_HEDLEY_MSVC_VERSION_CHECK(19,0,0) - #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES __pragma(warning(disable:5030)) -#elif JSON_HEDLEY_PGI_VERSION_CHECK(20,7,0) - #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma("diag_suppress 1097,1098") -#elif JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0) - #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma("diag_suppress 1097") -#elif JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,14,0) && defined(__cplusplus) - #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma("error_messages(off,attrskipunsup)") -#elif \ - JSON_HEDLEY_TI_VERSION_CHECK(18,1,0) || \ - JSON_HEDLEY_TI_CL6X_VERSION_CHECK(8,3,0) || \ - JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) - #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma("diag_suppress 1173") -#elif JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) - #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma("diag_suppress=Pe1097") -#elif JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) - #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma("diag_suppress 1097") -#else - #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES -#endif - -#if defined(JSON_HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL) - #undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL -#endif -#if JSON_HEDLEY_HAS_WARNING("-Wcast-qual") - #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL _Pragma("clang diagnostic ignored \"-Wcast-qual\"") -#elif JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) - #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL _Pragma("warning(disable:2203 2331)") -#elif JSON_HEDLEY_GCC_VERSION_CHECK(3,0,0) - #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL _Pragma("GCC diagnostic ignored \"-Wcast-qual\"") -#else - #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL -#endif - -#if defined(JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNUSED_FUNCTION) - #undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNUSED_FUNCTION -#endif -#if JSON_HEDLEY_HAS_WARNING("-Wunused-function") - #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNUSED_FUNCTION _Pragma("clang diagnostic ignored \"-Wunused-function\"") -#elif JSON_HEDLEY_GCC_VERSION_CHECK(3,4,0) - #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNUSED_FUNCTION _Pragma("GCC diagnostic ignored \"-Wunused-function\"") -#elif JSON_HEDLEY_MSVC_VERSION_CHECK(1,0,0) - #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNUSED_FUNCTION __pragma(warning(disable:4505)) -#elif JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) - #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNUSED_FUNCTION _Pragma("diag_suppress 3142") -#else - #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNUSED_FUNCTION -#endif - -#if defined(JSON_HEDLEY_DEPRECATED) - #undef JSON_HEDLEY_DEPRECATED -#endif -#if defined(JSON_HEDLEY_DEPRECATED_FOR) - #undef JSON_HEDLEY_DEPRECATED_FOR -#endif -#if \ - JSON_HEDLEY_MSVC_VERSION_CHECK(14,0,0) || \ - JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) - #define JSON_HEDLEY_DEPRECATED(since) __declspec(deprecated("Since " # since)) - #define JSON_HEDLEY_DEPRECATED_FOR(since, replacement) __declspec(deprecated("Since " #since "; use " #replacement)) -#elif \ - (JSON_HEDLEY_HAS_EXTENSION(attribute_deprecated_with_message) && !defined(JSON_HEDLEY_IAR_VERSION)) || \ - JSON_HEDLEY_GCC_VERSION_CHECK(4,5,0) || \ - JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ - JSON_HEDLEY_ARM_VERSION_CHECK(5,6,0) || \ - JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,13,0) || \ - JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0) || \ - JSON_HEDLEY_TI_VERSION_CHECK(18,1,0) || \ - JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(18,1,0) || \ - JSON_HEDLEY_TI_CL6X_VERSION_CHECK(8,3,0) || \ - JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ - JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,3,0) || \ - JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) - #define JSON_HEDLEY_DEPRECATED(since) __attribute__((__deprecated__("Since " #since))) - #define JSON_HEDLEY_DEPRECATED_FOR(since, replacement) __attribute__((__deprecated__("Since " #since "; use " #replacement))) -#elif defined(__cplusplus) && (__cplusplus >= 201402L) - #define JSON_HEDLEY_DEPRECATED(since) JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[deprecated("Since " #since)]]) - #define JSON_HEDLEY_DEPRECATED_FOR(since, replacement) JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[deprecated("Since " #since "; use " #replacement)]]) -#elif \ - JSON_HEDLEY_HAS_ATTRIBUTE(deprecated) || \ - JSON_HEDLEY_GCC_VERSION_CHECK(3,1,0) || \ - JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ - JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ - (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ - JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ - (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ - JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ - (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ - JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ - (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ - JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ - JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ - JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ - JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) || \ - JSON_HEDLEY_IAR_VERSION_CHECK(8,10,0) - #define JSON_HEDLEY_DEPRECATED(since) __attribute__((__deprecated__)) - #define JSON_HEDLEY_DEPRECATED_FOR(since, replacement) __attribute__((__deprecated__)) -#elif \ - JSON_HEDLEY_MSVC_VERSION_CHECK(13,10,0) || \ - JSON_HEDLEY_PELLES_VERSION_CHECK(6,50,0) || \ - JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) - #define JSON_HEDLEY_DEPRECATED(since) __declspec(deprecated) - #define JSON_HEDLEY_DEPRECATED_FOR(since, replacement) __declspec(deprecated) -#elif JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) - #define JSON_HEDLEY_DEPRECATED(since) _Pragma("deprecated") - #define JSON_HEDLEY_DEPRECATED_FOR(since, replacement) _Pragma("deprecated") -#else - #define JSON_HEDLEY_DEPRECATED(since) - #define JSON_HEDLEY_DEPRECATED_FOR(since, replacement) -#endif - -#if defined(JSON_HEDLEY_UNAVAILABLE) - #undef JSON_HEDLEY_UNAVAILABLE -#endif -#if \ - JSON_HEDLEY_HAS_ATTRIBUTE(warning) || \ - JSON_HEDLEY_GCC_VERSION_CHECK(4,3,0) || \ - JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ - JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) - #define JSON_HEDLEY_UNAVAILABLE(available_since) __attribute__((__warning__("Not available until " #available_since))) -#else - #define JSON_HEDLEY_UNAVAILABLE(available_since) -#endif - -#if defined(JSON_HEDLEY_WARN_UNUSED_RESULT) - #undef JSON_HEDLEY_WARN_UNUSED_RESULT -#endif -#if defined(JSON_HEDLEY_WARN_UNUSED_RESULT_MSG) - #undef JSON_HEDLEY_WARN_UNUSED_RESULT_MSG -#endif -#if \ - JSON_HEDLEY_HAS_ATTRIBUTE(warn_unused_result) || \ - JSON_HEDLEY_GCC_VERSION_CHECK(3,4,0) || \ - JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ - JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ - (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ - JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ - (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ - JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ - (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ - JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ - (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ - JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ - JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ - JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ - (JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,15,0) && defined(__cplusplus)) || \ - JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0) || \ - JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) - #define JSON_HEDLEY_WARN_UNUSED_RESULT __attribute__((__warn_unused_result__)) - #define JSON_HEDLEY_WARN_UNUSED_RESULT_MSG(msg) __attribute__((__warn_unused_result__)) -#elif (JSON_HEDLEY_HAS_CPP_ATTRIBUTE(nodiscard) >= 201907L) - #define JSON_HEDLEY_WARN_UNUSED_RESULT JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[nodiscard]]) - #define JSON_HEDLEY_WARN_UNUSED_RESULT_MSG(msg) JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[nodiscard(msg)]]) -#elif JSON_HEDLEY_HAS_CPP_ATTRIBUTE(nodiscard) - #define JSON_HEDLEY_WARN_UNUSED_RESULT JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[nodiscard]]) - #define JSON_HEDLEY_WARN_UNUSED_RESULT_MSG(msg) JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[nodiscard]]) -#elif defined(_Check_return_) /* SAL */ - #define JSON_HEDLEY_WARN_UNUSED_RESULT _Check_return_ - #define JSON_HEDLEY_WARN_UNUSED_RESULT_MSG(msg) _Check_return_ -#else - #define JSON_HEDLEY_WARN_UNUSED_RESULT - #define JSON_HEDLEY_WARN_UNUSED_RESULT_MSG(msg) -#endif - -#if defined(JSON_HEDLEY_SENTINEL) - #undef JSON_HEDLEY_SENTINEL -#endif -#if \ - JSON_HEDLEY_HAS_ATTRIBUTE(sentinel) || \ - JSON_HEDLEY_GCC_VERSION_CHECK(4,0,0) || \ - JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ - JSON_HEDLEY_ARM_VERSION_CHECK(5,4,0) || \ - JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) - #define JSON_HEDLEY_SENTINEL(position) __attribute__((__sentinel__(position))) -#else - #define JSON_HEDLEY_SENTINEL(position) -#endif - -#if defined(JSON_HEDLEY_NO_RETURN) - #undef JSON_HEDLEY_NO_RETURN -#endif -#if JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) - #define JSON_HEDLEY_NO_RETURN __noreturn -#elif \ - JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ - JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) - #define JSON_HEDLEY_NO_RETURN __attribute__((__noreturn__)) -#elif defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L - #define JSON_HEDLEY_NO_RETURN _Noreturn -#elif defined(__cplusplus) && (__cplusplus >= 201103L) - #define JSON_HEDLEY_NO_RETURN JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[noreturn]]) -#elif \ - JSON_HEDLEY_HAS_ATTRIBUTE(noreturn) || \ - JSON_HEDLEY_GCC_VERSION_CHECK(3,2,0) || \ - JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,11,0) || \ - JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ - JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) || \ - JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ - (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ - JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ - (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ - JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ - (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ - JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ - (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ - JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ - JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ - JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ - JSON_HEDLEY_IAR_VERSION_CHECK(8,10,0) - #define JSON_HEDLEY_NO_RETURN __attribute__((__noreturn__)) -#elif JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,10,0) - #define JSON_HEDLEY_NO_RETURN _Pragma("does_not_return") -#elif \ - JSON_HEDLEY_MSVC_VERSION_CHECK(13,10,0) || \ - JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) - #define JSON_HEDLEY_NO_RETURN __declspec(noreturn) -#elif JSON_HEDLEY_TI_CL6X_VERSION_CHECK(6,0,0) && defined(__cplusplus) - #define JSON_HEDLEY_NO_RETURN _Pragma("FUNC_NEVER_RETURNS;") -#elif JSON_HEDLEY_COMPCERT_VERSION_CHECK(3,2,0) - #define JSON_HEDLEY_NO_RETURN __attribute((noreturn)) -#elif JSON_HEDLEY_PELLES_VERSION_CHECK(9,0,0) - #define JSON_HEDLEY_NO_RETURN __declspec(noreturn) -#else - #define JSON_HEDLEY_NO_RETURN -#endif - -#if defined(JSON_HEDLEY_NO_ESCAPE) - #undef JSON_HEDLEY_NO_ESCAPE -#endif -#if JSON_HEDLEY_HAS_ATTRIBUTE(noescape) - #define JSON_HEDLEY_NO_ESCAPE __attribute__((__noescape__)) -#else - #define JSON_HEDLEY_NO_ESCAPE -#endif - -#if defined(JSON_HEDLEY_UNREACHABLE) - #undef JSON_HEDLEY_UNREACHABLE -#endif -#if defined(JSON_HEDLEY_UNREACHABLE_RETURN) - #undef JSON_HEDLEY_UNREACHABLE_RETURN -#endif -#if defined(JSON_HEDLEY_ASSUME) - #undef JSON_HEDLEY_ASSUME -#endif -#if \ - JSON_HEDLEY_MSVC_VERSION_CHECK(13,10,0) || \ - JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ - JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) - #define JSON_HEDLEY_ASSUME(expr) __assume(expr) -#elif JSON_HEDLEY_HAS_BUILTIN(__builtin_assume) - #define JSON_HEDLEY_ASSUME(expr) __builtin_assume(expr) -#elif \ - JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,2,0) || \ - JSON_HEDLEY_TI_CL6X_VERSION_CHECK(4,0,0) - #if defined(__cplusplus) - #define JSON_HEDLEY_ASSUME(expr) std::_nassert(expr) - #else - #define JSON_HEDLEY_ASSUME(expr) _nassert(expr) - #endif -#endif -#if \ - (JSON_HEDLEY_HAS_BUILTIN(__builtin_unreachable) && (!defined(JSON_HEDLEY_ARM_VERSION))) || \ - JSON_HEDLEY_GCC_VERSION_CHECK(4,5,0) || \ - JSON_HEDLEY_PGI_VERSION_CHECK(18,10,0) || \ - JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ - JSON_HEDLEY_IBM_VERSION_CHECK(13,1,5) || \ - JSON_HEDLEY_CRAY_VERSION_CHECK(10,0,0) || \ - JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) - #define JSON_HEDLEY_UNREACHABLE() __builtin_unreachable() -#elif defined(JSON_HEDLEY_ASSUME) - #define JSON_HEDLEY_UNREACHABLE() JSON_HEDLEY_ASSUME(0) -#endif -#if !defined(JSON_HEDLEY_ASSUME) - #if defined(JSON_HEDLEY_UNREACHABLE) - #define JSON_HEDLEY_ASSUME(expr) JSON_HEDLEY_STATIC_CAST(void, ((expr) ? 1 : (JSON_HEDLEY_UNREACHABLE(), 1))) - #else - #define JSON_HEDLEY_ASSUME(expr) JSON_HEDLEY_STATIC_CAST(void, expr) - #endif -#endif -#if defined(JSON_HEDLEY_UNREACHABLE) - #if \ - JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,2,0) || \ - JSON_HEDLEY_TI_CL6X_VERSION_CHECK(4,0,0) - #define JSON_HEDLEY_UNREACHABLE_RETURN(value) return (JSON_HEDLEY_STATIC_CAST(void, JSON_HEDLEY_ASSUME(0)), (value)) - #else - #define JSON_HEDLEY_UNREACHABLE_RETURN(value) JSON_HEDLEY_UNREACHABLE() - #endif -#else - #define JSON_HEDLEY_UNREACHABLE_RETURN(value) return (value) -#endif -#if !defined(JSON_HEDLEY_UNREACHABLE) - #define JSON_HEDLEY_UNREACHABLE() JSON_HEDLEY_ASSUME(0) -#endif - -JSON_HEDLEY_DIAGNOSTIC_PUSH -#if JSON_HEDLEY_HAS_WARNING("-Wpedantic") - #pragma clang diagnostic ignored "-Wpedantic" -#endif -#if JSON_HEDLEY_HAS_WARNING("-Wc++98-compat-pedantic") && defined(__cplusplus) - #pragma clang diagnostic ignored "-Wc++98-compat-pedantic" -#endif -#if JSON_HEDLEY_GCC_HAS_WARNING("-Wvariadic-macros",4,0,0) - #if defined(__clang__) - #pragma clang diagnostic ignored "-Wvariadic-macros" - #elif defined(JSON_HEDLEY_GCC_VERSION) - #pragma GCC diagnostic ignored "-Wvariadic-macros" - #endif -#endif -#if defined(JSON_HEDLEY_NON_NULL) - #undef JSON_HEDLEY_NON_NULL -#endif -#if \ - JSON_HEDLEY_HAS_ATTRIBUTE(nonnull) || \ - JSON_HEDLEY_GCC_VERSION_CHECK(3,3,0) || \ - JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ - JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) - #define JSON_HEDLEY_NON_NULL(...) __attribute__((__nonnull__(__VA_ARGS__))) -#else - #define JSON_HEDLEY_NON_NULL(...) -#endif -JSON_HEDLEY_DIAGNOSTIC_POP - -#if defined(JSON_HEDLEY_PRINTF_FORMAT) - #undef JSON_HEDLEY_PRINTF_FORMAT -#endif -#if defined(__MINGW32__) && JSON_HEDLEY_GCC_HAS_ATTRIBUTE(format,4,4,0) && !defined(__USE_MINGW_ANSI_STDIO) - #define JSON_HEDLEY_PRINTF_FORMAT(string_idx,first_to_check) __attribute__((__format__(ms_printf, string_idx, first_to_check))) -#elif defined(__MINGW32__) && JSON_HEDLEY_GCC_HAS_ATTRIBUTE(format,4,4,0) && defined(__USE_MINGW_ANSI_STDIO) - #define JSON_HEDLEY_PRINTF_FORMAT(string_idx,first_to_check) __attribute__((__format__(gnu_printf, string_idx, first_to_check))) -#elif \ - JSON_HEDLEY_HAS_ATTRIBUTE(format) || \ - JSON_HEDLEY_GCC_VERSION_CHECK(3,1,0) || \ - JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ - JSON_HEDLEY_ARM_VERSION_CHECK(5,6,0) || \ - JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) || \ - JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ - (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ - JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ - (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ - JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ - (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ - JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ - (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ - JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ - JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ - JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ - JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) - #define JSON_HEDLEY_PRINTF_FORMAT(string_idx,first_to_check) __attribute__((__format__(__printf__, string_idx, first_to_check))) -#elif JSON_HEDLEY_PELLES_VERSION_CHECK(6,0,0) - #define JSON_HEDLEY_PRINTF_FORMAT(string_idx,first_to_check) __declspec(vaformat(printf,string_idx,first_to_check)) -#else - #define JSON_HEDLEY_PRINTF_FORMAT(string_idx,first_to_check) -#endif - -#if defined(JSON_HEDLEY_CONSTEXPR) - #undef JSON_HEDLEY_CONSTEXPR -#endif -#if defined(__cplusplus) - #if __cplusplus >= 201103L - #define JSON_HEDLEY_CONSTEXPR JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_(constexpr) - #endif -#endif -#if !defined(JSON_HEDLEY_CONSTEXPR) - #define JSON_HEDLEY_CONSTEXPR -#endif - -#if defined(JSON_HEDLEY_PREDICT) - #undef JSON_HEDLEY_PREDICT -#endif -#if defined(JSON_HEDLEY_LIKELY) - #undef JSON_HEDLEY_LIKELY -#endif -#if defined(JSON_HEDLEY_UNLIKELY) - #undef JSON_HEDLEY_UNLIKELY -#endif -#if defined(JSON_HEDLEY_UNPREDICTABLE) - #undef JSON_HEDLEY_UNPREDICTABLE -#endif -#if JSON_HEDLEY_HAS_BUILTIN(__builtin_unpredictable) - #define JSON_HEDLEY_UNPREDICTABLE(expr) __builtin_unpredictable((expr)) -#endif -#if \ - (JSON_HEDLEY_HAS_BUILTIN(__builtin_expect_with_probability) && !defined(JSON_HEDLEY_PGI_VERSION)) || \ - JSON_HEDLEY_GCC_VERSION_CHECK(9,0,0) || \ - JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) -# define JSON_HEDLEY_PREDICT(expr, value, probability) __builtin_expect_with_probability( (expr), (value), (probability)) -# define JSON_HEDLEY_PREDICT_TRUE(expr, probability) __builtin_expect_with_probability(!!(expr), 1 , (probability)) -# define JSON_HEDLEY_PREDICT_FALSE(expr, probability) __builtin_expect_with_probability(!!(expr), 0 , (probability)) -# define JSON_HEDLEY_LIKELY(expr) __builtin_expect (!!(expr), 1 ) -# define JSON_HEDLEY_UNLIKELY(expr) __builtin_expect (!!(expr), 0 ) -#elif \ - (JSON_HEDLEY_HAS_BUILTIN(__builtin_expect) && !defined(JSON_HEDLEY_INTEL_CL_VERSION)) || \ - JSON_HEDLEY_GCC_VERSION_CHECK(3,0,0) || \ - JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ - (JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,15,0) && defined(__cplusplus)) || \ - JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ - JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) || \ - JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ - JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,7,0) || \ - JSON_HEDLEY_TI_CL430_VERSION_CHECK(3,1,0) || \ - JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,1,0) || \ - JSON_HEDLEY_TI_CL6X_VERSION_CHECK(6,1,0) || \ - JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ - JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ - JSON_HEDLEY_TINYC_VERSION_CHECK(0,9,27) || \ - JSON_HEDLEY_CRAY_VERSION_CHECK(8,1,0) || \ - JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) -# define JSON_HEDLEY_PREDICT(expr, expected, probability) \ - (((probability) >= 0.9) ? __builtin_expect((expr), (expected)) : (JSON_HEDLEY_STATIC_CAST(void, expected), (expr))) -# define JSON_HEDLEY_PREDICT_TRUE(expr, probability) \ - (__extension__ ({ \ - double hedley_probability_ = (probability); \ - ((hedley_probability_ >= 0.9) ? __builtin_expect(!!(expr), 1) : ((hedley_probability_ <= 0.1) ? __builtin_expect(!!(expr), 0) : !!(expr))); \ - })) -# define JSON_HEDLEY_PREDICT_FALSE(expr, probability) \ - (__extension__ ({ \ - double hedley_probability_ = (probability); \ - ((hedley_probability_ >= 0.9) ? __builtin_expect(!!(expr), 0) : ((hedley_probability_ <= 0.1) ? __builtin_expect(!!(expr), 1) : !!(expr))); \ - })) -# define JSON_HEDLEY_LIKELY(expr) __builtin_expect(!!(expr), 1) -# define JSON_HEDLEY_UNLIKELY(expr) __builtin_expect(!!(expr), 0) -#else -# define JSON_HEDLEY_PREDICT(expr, expected, probability) (JSON_HEDLEY_STATIC_CAST(void, expected), (expr)) -# define JSON_HEDLEY_PREDICT_TRUE(expr, probability) (!!(expr)) -# define JSON_HEDLEY_PREDICT_FALSE(expr, probability) (!!(expr)) -# define JSON_HEDLEY_LIKELY(expr) (!!(expr)) -# define JSON_HEDLEY_UNLIKELY(expr) (!!(expr)) -#endif -#if !defined(JSON_HEDLEY_UNPREDICTABLE) - #define JSON_HEDLEY_UNPREDICTABLE(expr) JSON_HEDLEY_PREDICT(expr, 1, 0.5) -#endif - -#if defined(JSON_HEDLEY_MALLOC) - #undef JSON_HEDLEY_MALLOC -#endif -#if \ - JSON_HEDLEY_HAS_ATTRIBUTE(malloc) || \ - JSON_HEDLEY_GCC_VERSION_CHECK(3,1,0) || \ - JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ - JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,11,0) || \ - JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ - JSON_HEDLEY_IBM_VERSION_CHECK(12,1,0) || \ - JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ - (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ - JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ - (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ - JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ - (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ - JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ - (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ - JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ - JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ - JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ - JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) - #define JSON_HEDLEY_MALLOC __attribute__((__malloc__)) -#elif JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,10,0) - #define JSON_HEDLEY_MALLOC _Pragma("returns_new_memory") -#elif \ - JSON_HEDLEY_MSVC_VERSION_CHECK(14,0,0) || \ - JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) - #define JSON_HEDLEY_MALLOC __declspec(restrict) -#else - #define JSON_HEDLEY_MALLOC -#endif - -#if defined(JSON_HEDLEY_PURE) - #undef JSON_HEDLEY_PURE -#endif -#if \ - JSON_HEDLEY_HAS_ATTRIBUTE(pure) || \ - JSON_HEDLEY_GCC_VERSION_CHECK(2,96,0) || \ - JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ - JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,11,0) || \ - JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ - JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) || \ - JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ - (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ - JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ - (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ - JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ - (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ - JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ - (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ - JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ - JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ - JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ - JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0) || \ - JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) -# define JSON_HEDLEY_PURE __attribute__((__pure__)) -#elif JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,10,0) -# define JSON_HEDLEY_PURE _Pragma("does_not_write_global_data") -#elif defined(__cplusplus) && \ - ( \ - JSON_HEDLEY_TI_CL430_VERSION_CHECK(2,0,1) || \ - JSON_HEDLEY_TI_CL6X_VERSION_CHECK(4,0,0) || \ - JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) \ - ) -# define JSON_HEDLEY_PURE _Pragma("FUNC_IS_PURE;") -#else -# define JSON_HEDLEY_PURE -#endif - -#if defined(JSON_HEDLEY_CONST) - #undef JSON_HEDLEY_CONST -#endif -#if \ - JSON_HEDLEY_HAS_ATTRIBUTE(const) || \ - JSON_HEDLEY_GCC_VERSION_CHECK(2,5,0) || \ - JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ - JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,11,0) || \ - JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ - JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) || \ - JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ - (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ - JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ - (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ - JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ - (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ - JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ - (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ - JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ - JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ - JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ - JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0) || \ - JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) - #define JSON_HEDLEY_CONST __attribute__((__const__)) -#elif \ - JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,10,0) - #define JSON_HEDLEY_CONST _Pragma("no_side_effect") -#else - #define JSON_HEDLEY_CONST JSON_HEDLEY_PURE -#endif - -#if defined(JSON_HEDLEY_RESTRICT) - #undef JSON_HEDLEY_RESTRICT -#endif -#if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) && !defined(__cplusplus) - #define JSON_HEDLEY_RESTRICT restrict -#elif \ - JSON_HEDLEY_GCC_VERSION_CHECK(3,1,0) || \ - JSON_HEDLEY_MSVC_VERSION_CHECK(14,0,0) || \ - JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ - JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) || \ - JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ - JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) || \ - JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0) || \ - JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ - JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,2,4) || \ - JSON_HEDLEY_TI_CL6X_VERSION_CHECK(8,1,0) || \ - JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ - (JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,14,0) && defined(__cplusplus)) || \ - JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) || \ - defined(__clang__) || \ - JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) - #define JSON_HEDLEY_RESTRICT __restrict -#elif JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,3,0) && !defined(__cplusplus) - #define JSON_HEDLEY_RESTRICT _Restrict -#else - #define JSON_HEDLEY_RESTRICT -#endif - -#if defined(JSON_HEDLEY_INLINE) - #undef JSON_HEDLEY_INLINE -#endif -#if \ - (defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L)) || \ - (defined(__cplusplus) && (__cplusplus >= 199711L)) - #define JSON_HEDLEY_INLINE inline -#elif \ - defined(JSON_HEDLEY_GCC_VERSION) || \ - JSON_HEDLEY_ARM_VERSION_CHECK(6,2,0) - #define JSON_HEDLEY_INLINE __inline__ -#elif \ - JSON_HEDLEY_MSVC_VERSION_CHECK(12,0,0) || \ - JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) || \ - JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ - JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,1,0) || \ - JSON_HEDLEY_TI_CL430_VERSION_CHECK(3,1,0) || \ - JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,2,0) || \ - JSON_HEDLEY_TI_CL6X_VERSION_CHECK(8,0,0) || \ - JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ - JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ - JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) - #define JSON_HEDLEY_INLINE __inline -#else - #define JSON_HEDLEY_INLINE -#endif - -#if defined(JSON_HEDLEY_ALWAYS_INLINE) - #undef JSON_HEDLEY_ALWAYS_INLINE -#endif -#if \ - JSON_HEDLEY_HAS_ATTRIBUTE(always_inline) || \ - JSON_HEDLEY_GCC_VERSION_CHECK(4,0,0) || \ - JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ - JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,11,0) || \ - JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ - JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) || \ - JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ - (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ - JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ - (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ - JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ - (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ - JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ - (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ - JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ - JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ - JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ - JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) || \ - JSON_HEDLEY_IAR_VERSION_CHECK(8,10,0) -# define JSON_HEDLEY_ALWAYS_INLINE __attribute__((__always_inline__)) JSON_HEDLEY_INLINE -#elif \ - JSON_HEDLEY_MSVC_VERSION_CHECK(12,0,0) || \ - JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) -# define JSON_HEDLEY_ALWAYS_INLINE __forceinline -#elif defined(__cplusplus) && \ - ( \ - JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ - JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ - JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ - JSON_HEDLEY_TI_CL6X_VERSION_CHECK(6,1,0) || \ - JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ - JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) \ - ) -# define JSON_HEDLEY_ALWAYS_INLINE _Pragma("FUNC_ALWAYS_INLINE;") -#elif JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) -# define JSON_HEDLEY_ALWAYS_INLINE _Pragma("inline=forced") -#else -# define JSON_HEDLEY_ALWAYS_INLINE JSON_HEDLEY_INLINE -#endif - -#if defined(JSON_HEDLEY_NEVER_INLINE) - #undef JSON_HEDLEY_NEVER_INLINE -#endif -#if \ - JSON_HEDLEY_HAS_ATTRIBUTE(noinline) || \ - JSON_HEDLEY_GCC_VERSION_CHECK(4,0,0) || \ - JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ - JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,11,0) || \ - JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ - JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) || \ - JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ - (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ - JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ - (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ - JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ - (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ - JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ - (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ - JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ - JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ - JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ - JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) || \ - JSON_HEDLEY_IAR_VERSION_CHECK(8,10,0) - #define JSON_HEDLEY_NEVER_INLINE __attribute__((__noinline__)) -#elif \ - JSON_HEDLEY_MSVC_VERSION_CHECK(13,10,0) || \ - JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) - #define JSON_HEDLEY_NEVER_INLINE __declspec(noinline) -#elif JSON_HEDLEY_PGI_VERSION_CHECK(10,2,0) - #define JSON_HEDLEY_NEVER_INLINE _Pragma("noinline") -#elif JSON_HEDLEY_TI_CL6X_VERSION_CHECK(6,0,0) && defined(__cplusplus) - #define JSON_HEDLEY_NEVER_INLINE _Pragma("FUNC_CANNOT_INLINE;") -#elif JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) - #define JSON_HEDLEY_NEVER_INLINE _Pragma("inline=never") -#elif JSON_HEDLEY_COMPCERT_VERSION_CHECK(3,2,0) - #define JSON_HEDLEY_NEVER_INLINE __attribute((noinline)) -#elif JSON_HEDLEY_PELLES_VERSION_CHECK(9,0,0) - #define JSON_HEDLEY_NEVER_INLINE __declspec(noinline) -#else - #define JSON_HEDLEY_NEVER_INLINE -#endif - -#if defined(JSON_HEDLEY_PRIVATE) - #undef JSON_HEDLEY_PRIVATE -#endif -#if defined(JSON_HEDLEY_PUBLIC) - #undef JSON_HEDLEY_PUBLIC -#endif -#if defined(JSON_HEDLEY_IMPORT) - #undef JSON_HEDLEY_IMPORT -#endif -#if defined(_WIN32) || defined(__CYGWIN__) -# define JSON_HEDLEY_PRIVATE -# define JSON_HEDLEY_PUBLIC __declspec(dllexport) -# define JSON_HEDLEY_IMPORT __declspec(dllimport) -#else -# if \ - JSON_HEDLEY_HAS_ATTRIBUTE(visibility) || \ - JSON_HEDLEY_GCC_VERSION_CHECK(3,3,0) || \ - JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,11,0) || \ - JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ - JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ - JSON_HEDLEY_IBM_VERSION_CHECK(13,1,0) || \ - ( \ - defined(__TI_EABI__) && \ - ( \ - (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ - JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) \ - ) \ - ) || \ - JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) -# define JSON_HEDLEY_PRIVATE __attribute__((__visibility__("hidden"))) -# define JSON_HEDLEY_PUBLIC __attribute__((__visibility__("default"))) -# else -# define JSON_HEDLEY_PRIVATE -# define JSON_HEDLEY_PUBLIC -# endif -# define JSON_HEDLEY_IMPORT extern -#endif - -#if defined(JSON_HEDLEY_NO_THROW) - #undef JSON_HEDLEY_NO_THROW -#endif -#if \ - JSON_HEDLEY_HAS_ATTRIBUTE(nothrow) || \ - JSON_HEDLEY_GCC_VERSION_CHECK(3,3,0) || \ - JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ - JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) - #define JSON_HEDLEY_NO_THROW __attribute__((__nothrow__)) -#elif \ - JSON_HEDLEY_MSVC_VERSION_CHECK(13,1,0) || \ - JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) || \ - JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) - #define JSON_HEDLEY_NO_THROW __declspec(nothrow) -#else - #define JSON_HEDLEY_NO_THROW -#endif - -#if defined(JSON_HEDLEY_FALL_THROUGH) - #undef JSON_HEDLEY_FALL_THROUGH -#endif -#if \ - JSON_HEDLEY_HAS_ATTRIBUTE(fallthrough) || \ - JSON_HEDLEY_GCC_VERSION_CHECK(7,0,0) || \ - JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) - #define JSON_HEDLEY_FALL_THROUGH __attribute__((__fallthrough__)) -#elif JSON_HEDLEY_HAS_CPP_ATTRIBUTE_NS(clang,fallthrough) - #define JSON_HEDLEY_FALL_THROUGH JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[clang::fallthrough]]) -#elif JSON_HEDLEY_HAS_CPP_ATTRIBUTE(fallthrough) - #define JSON_HEDLEY_FALL_THROUGH JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[fallthrough]]) -#elif defined(__fallthrough) /* SAL */ - #define JSON_HEDLEY_FALL_THROUGH __fallthrough -#else - #define JSON_HEDLEY_FALL_THROUGH -#endif - -#if defined(JSON_HEDLEY_RETURNS_NON_NULL) - #undef JSON_HEDLEY_RETURNS_NON_NULL -#endif -#if \ - JSON_HEDLEY_HAS_ATTRIBUTE(returns_nonnull) || \ - JSON_HEDLEY_GCC_VERSION_CHECK(4,9,0) || \ - JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) - #define JSON_HEDLEY_RETURNS_NON_NULL __attribute__((__returns_nonnull__)) -#elif defined(_Ret_notnull_) /* SAL */ - #define JSON_HEDLEY_RETURNS_NON_NULL _Ret_notnull_ -#else - #define JSON_HEDLEY_RETURNS_NON_NULL -#endif - -#if defined(JSON_HEDLEY_ARRAY_PARAM) - #undef JSON_HEDLEY_ARRAY_PARAM -#endif -#if \ - defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) && \ - !defined(__STDC_NO_VLA__) && \ - !defined(__cplusplus) && \ - !defined(JSON_HEDLEY_PGI_VERSION) && \ - !defined(JSON_HEDLEY_TINYC_VERSION) - #define JSON_HEDLEY_ARRAY_PARAM(name) (name) -#else - #define JSON_HEDLEY_ARRAY_PARAM(name) -#endif - -#if defined(JSON_HEDLEY_IS_CONSTANT) - #undef JSON_HEDLEY_IS_CONSTANT -#endif -#if defined(JSON_HEDLEY_REQUIRE_CONSTEXPR) - #undef JSON_HEDLEY_REQUIRE_CONSTEXPR -#endif -/* JSON_HEDLEY_IS_CONSTEXPR_ is for - HEDLEY INTERNAL USE ONLY. API subject to change without notice. */ -#if defined(JSON_HEDLEY_IS_CONSTEXPR_) - #undef JSON_HEDLEY_IS_CONSTEXPR_ -#endif -#if \ - JSON_HEDLEY_HAS_BUILTIN(__builtin_constant_p) || \ - JSON_HEDLEY_GCC_VERSION_CHECK(3,4,0) || \ - JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ - JSON_HEDLEY_TINYC_VERSION_CHECK(0,9,19) || \ - JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ - JSON_HEDLEY_IBM_VERSION_CHECK(13,1,0) || \ - JSON_HEDLEY_TI_CL6X_VERSION_CHECK(6,1,0) || \ - (JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,10,0) && !defined(__cplusplus)) || \ - JSON_HEDLEY_CRAY_VERSION_CHECK(8,1,0) || \ - JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) - #define JSON_HEDLEY_IS_CONSTANT(expr) __builtin_constant_p(expr) -#endif -#if !defined(__cplusplus) -# if \ - JSON_HEDLEY_HAS_BUILTIN(__builtin_types_compatible_p) || \ - JSON_HEDLEY_GCC_VERSION_CHECK(3,4,0) || \ - JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ - JSON_HEDLEY_IBM_VERSION_CHECK(13,1,0) || \ - JSON_HEDLEY_CRAY_VERSION_CHECK(8,1,0) || \ - JSON_HEDLEY_ARM_VERSION_CHECK(5,4,0) || \ - JSON_HEDLEY_TINYC_VERSION_CHECK(0,9,24) -#if defined(__INTPTR_TYPE__) - #define JSON_HEDLEY_IS_CONSTEXPR_(expr) __builtin_types_compatible_p(__typeof__((1 ? (void*) ((__INTPTR_TYPE__) ((expr) * 0)) : (int*) 0)), int*) -#else - #include - #define JSON_HEDLEY_IS_CONSTEXPR_(expr) __builtin_types_compatible_p(__typeof__((1 ? (void*) ((intptr_t) ((expr) * 0)) : (int*) 0)), int*) -#endif -# elif \ - ( \ - defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L) && \ - !defined(JSON_HEDLEY_SUNPRO_VERSION) && \ - !defined(JSON_HEDLEY_PGI_VERSION) && \ - !defined(JSON_HEDLEY_IAR_VERSION)) || \ - (JSON_HEDLEY_HAS_EXTENSION(c_generic_selections) && !defined(JSON_HEDLEY_IAR_VERSION)) || \ - JSON_HEDLEY_GCC_VERSION_CHECK(4,9,0) || \ - JSON_HEDLEY_INTEL_VERSION_CHECK(17,0,0) || \ - JSON_HEDLEY_IBM_VERSION_CHECK(12,1,0) || \ - JSON_HEDLEY_ARM_VERSION_CHECK(5,3,0) -#if defined(__INTPTR_TYPE__) - #define JSON_HEDLEY_IS_CONSTEXPR_(expr) _Generic((1 ? (void*) ((__INTPTR_TYPE__) ((expr) * 0)) : (int*) 0), int*: 1, void*: 0) -#else - #include - #define JSON_HEDLEY_IS_CONSTEXPR_(expr) _Generic((1 ? (void*) ((intptr_t) * 0) : (int*) 0), int*: 1, void*: 0) -#endif -# elif \ - defined(JSON_HEDLEY_GCC_VERSION) || \ - defined(JSON_HEDLEY_INTEL_VERSION) || \ - defined(JSON_HEDLEY_TINYC_VERSION) || \ - defined(JSON_HEDLEY_TI_ARMCL_VERSION) || \ - JSON_HEDLEY_TI_CL430_VERSION_CHECK(18,12,0) || \ - defined(JSON_HEDLEY_TI_CL2000_VERSION) || \ - defined(JSON_HEDLEY_TI_CL6X_VERSION) || \ - defined(JSON_HEDLEY_TI_CL7X_VERSION) || \ - defined(JSON_HEDLEY_TI_CLPRU_VERSION) || \ - defined(__clang__) -# define JSON_HEDLEY_IS_CONSTEXPR_(expr) ( \ - sizeof(void) != \ - sizeof(*( \ - 1 ? \ - ((void*) ((expr) * 0L) ) : \ -((struct { char v[sizeof(void) * 2]; } *) 1) \ - ) \ - ) \ - ) -# endif -#endif -#if defined(JSON_HEDLEY_IS_CONSTEXPR_) - #if !defined(JSON_HEDLEY_IS_CONSTANT) - #define JSON_HEDLEY_IS_CONSTANT(expr) JSON_HEDLEY_IS_CONSTEXPR_(expr) - #endif - #define JSON_HEDLEY_REQUIRE_CONSTEXPR(expr) (JSON_HEDLEY_IS_CONSTEXPR_(expr) ? (expr) : (-1)) -#else - #if !defined(JSON_HEDLEY_IS_CONSTANT) - #define JSON_HEDLEY_IS_CONSTANT(expr) (0) - #endif - #define JSON_HEDLEY_REQUIRE_CONSTEXPR(expr) (expr) -#endif - -#if defined(JSON_HEDLEY_BEGIN_C_DECLS) - #undef JSON_HEDLEY_BEGIN_C_DECLS -#endif -#if defined(JSON_HEDLEY_END_C_DECLS) - #undef JSON_HEDLEY_END_C_DECLS -#endif -#if defined(JSON_HEDLEY_C_DECL) - #undef JSON_HEDLEY_C_DECL -#endif -#if defined(__cplusplus) - #define JSON_HEDLEY_BEGIN_C_DECLS extern "C" { - #define JSON_HEDLEY_END_C_DECLS } - #define JSON_HEDLEY_C_DECL extern "C" -#else - #define JSON_HEDLEY_BEGIN_C_DECLS - #define JSON_HEDLEY_END_C_DECLS - #define JSON_HEDLEY_C_DECL -#endif - -#if defined(JSON_HEDLEY_STATIC_ASSERT) - #undef JSON_HEDLEY_STATIC_ASSERT -#endif -#if \ - !defined(__cplusplus) && ( \ - (defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L)) || \ - (JSON_HEDLEY_HAS_FEATURE(c_static_assert) && !defined(JSON_HEDLEY_INTEL_CL_VERSION)) || \ - JSON_HEDLEY_GCC_VERSION_CHECK(6,0,0) || \ - JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ - defined(_Static_assert) \ - ) -# define JSON_HEDLEY_STATIC_ASSERT(expr, message) _Static_assert(expr, message) -#elif \ - (defined(__cplusplus) && (__cplusplus >= 201103L)) || \ - JSON_HEDLEY_MSVC_VERSION_CHECK(16,0,0) || \ - JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) -# define JSON_HEDLEY_STATIC_ASSERT(expr, message) JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_(static_assert(expr, message)) -#else -# define JSON_HEDLEY_STATIC_ASSERT(expr, message) -#endif - -#if defined(JSON_HEDLEY_NULL) - #undef JSON_HEDLEY_NULL -#endif -#if defined(__cplusplus) - #if __cplusplus >= 201103L - #define JSON_HEDLEY_NULL JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_(nullptr) - #elif defined(NULL) - #define JSON_HEDLEY_NULL NULL - #else - #define JSON_HEDLEY_NULL JSON_HEDLEY_STATIC_CAST(void*, 0) - #endif -#elif defined(NULL) - #define JSON_HEDLEY_NULL NULL -#else - #define JSON_HEDLEY_NULL ((void*) 0) -#endif - -#if defined(JSON_HEDLEY_MESSAGE) - #undef JSON_HEDLEY_MESSAGE -#endif -#if JSON_HEDLEY_HAS_WARNING("-Wunknown-pragmas") -# define JSON_HEDLEY_MESSAGE(msg) \ - JSON_HEDLEY_DIAGNOSTIC_PUSH \ - JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS \ - JSON_HEDLEY_PRAGMA(message msg) \ - JSON_HEDLEY_DIAGNOSTIC_POP -#elif \ - JSON_HEDLEY_GCC_VERSION_CHECK(4,4,0) || \ - JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) -# define JSON_HEDLEY_MESSAGE(msg) JSON_HEDLEY_PRAGMA(message msg) -#elif JSON_HEDLEY_CRAY_VERSION_CHECK(5,0,0) -# define JSON_HEDLEY_MESSAGE(msg) JSON_HEDLEY_PRAGMA(_CRI message msg) -#elif JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) -# define JSON_HEDLEY_MESSAGE(msg) JSON_HEDLEY_PRAGMA(message(msg)) -#elif JSON_HEDLEY_PELLES_VERSION_CHECK(2,0,0) -# define JSON_HEDLEY_MESSAGE(msg) JSON_HEDLEY_PRAGMA(message(msg)) -#else -# define JSON_HEDLEY_MESSAGE(msg) -#endif - -#if defined(JSON_HEDLEY_WARNING) - #undef JSON_HEDLEY_WARNING -#endif -#if JSON_HEDLEY_HAS_WARNING("-Wunknown-pragmas") -# define JSON_HEDLEY_WARNING(msg) \ - JSON_HEDLEY_DIAGNOSTIC_PUSH \ - JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS \ - JSON_HEDLEY_PRAGMA(clang warning msg) \ - JSON_HEDLEY_DIAGNOSTIC_POP -#elif \ - JSON_HEDLEY_GCC_VERSION_CHECK(4,8,0) || \ - JSON_HEDLEY_PGI_VERSION_CHECK(18,4,0) || \ - JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) -# define JSON_HEDLEY_WARNING(msg) JSON_HEDLEY_PRAGMA(GCC warning msg) -#elif \ - JSON_HEDLEY_MSVC_VERSION_CHECK(15,0,0) || \ - JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) -# define JSON_HEDLEY_WARNING(msg) JSON_HEDLEY_PRAGMA(message(msg)) -#else -# define JSON_HEDLEY_WARNING(msg) JSON_HEDLEY_MESSAGE(msg) -#endif - -#if defined(JSON_HEDLEY_REQUIRE) - #undef JSON_HEDLEY_REQUIRE -#endif -#if defined(JSON_HEDLEY_REQUIRE_MSG) - #undef JSON_HEDLEY_REQUIRE_MSG -#endif -#if JSON_HEDLEY_HAS_ATTRIBUTE(diagnose_if) -# if JSON_HEDLEY_HAS_WARNING("-Wgcc-compat") -# define JSON_HEDLEY_REQUIRE(expr) \ - JSON_HEDLEY_DIAGNOSTIC_PUSH \ - _Pragma("clang diagnostic ignored \"-Wgcc-compat\"") \ - __attribute__((diagnose_if(!(expr), #expr, "error"))) \ - JSON_HEDLEY_DIAGNOSTIC_POP -# define JSON_HEDLEY_REQUIRE_MSG(expr,msg) \ - JSON_HEDLEY_DIAGNOSTIC_PUSH \ - _Pragma("clang diagnostic ignored \"-Wgcc-compat\"") \ - __attribute__((diagnose_if(!(expr), msg, "error"))) \ - JSON_HEDLEY_DIAGNOSTIC_POP -# else -# define JSON_HEDLEY_REQUIRE(expr) __attribute__((diagnose_if(!(expr), #expr, "error"))) -# define JSON_HEDLEY_REQUIRE_MSG(expr,msg) __attribute__((diagnose_if(!(expr), msg, "error"))) -# endif -#else -# define JSON_HEDLEY_REQUIRE(expr) -# define JSON_HEDLEY_REQUIRE_MSG(expr,msg) -#endif - -#if defined(JSON_HEDLEY_FLAGS) - #undef JSON_HEDLEY_FLAGS -#endif -#if JSON_HEDLEY_HAS_ATTRIBUTE(flag_enum) && (!defined(__cplusplus) || JSON_HEDLEY_HAS_WARNING("-Wbitfield-enum-conversion")) - #define JSON_HEDLEY_FLAGS __attribute__((__flag_enum__)) -#else - #define JSON_HEDLEY_FLAGS -#endif - -#if defined(JSON_HEDLEY_FLAGS_CAST) - #undef JSON_HEDLEY_FLAGS_CAST -#endif -#if JSON_HEDLEY_INTEL_VERSION_CHECK(19,0,0) -# define JSON_HEDLEY_FLAGS_CAST(T, expr) (__extension__ ({ \ - JSON_HEDLEY_DIAGNOSTIC_PUSH \ - _Pragma("warning(disable:188)") \ - ((T) (expr)); \ - JSON_HEDLEY_DIAGNOSTIC_POP \ - })) -#else -# define JSON_HEDLEY_FLAGS_CAST(T, expr) JSON_HEDLEY_STATIC_CAST(T, expr) -#endif - -#if defined(JSON_HEDLEY_EMPTY_BASES) - #undef JSON_HEDLEY_EMPTY_BASES -#endif -#if \ - (JSON_HEDLEY_MSVC_VERSION_CHECK(19,0,23918) && !JSON_HEDLEY_MSVC_VERSION_CHECK(20,0,0)) || \ - JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) - #define JSON_HEDLEY_EMPTY_BASES __declspec(empty_bases) -#else - #define JSON_HEDLEY_EMPTY_BASES -#endif - -/* Remaining macros are deprecated. */ - -#if defined(JSON_HEDLEY_GCC_NOT_CLANG_VERSION_CHECK) - #undef JSON_HEDLEY_GCC_NOT_CLANG_VERSION_CHECK -#endif -#if defined(__clang__) - #define JSON_HEDLEY_GCC_NOT_CLANG_VERSION_CHECK(major,minor,patch) (0) -#else - #define JSON_HEDLEY_GCC_NOT_CLANG_VERSION_CHECK(major,minor,patch) JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) -#endif - -#if defined(JSON_HEDLEY_CLANG_HAS_ATTRIBUTE) - #undef JSON_HEDLEY_CLANG_HAS_ATTRIBUTE -#endif -#define JSON_HEDLEY_CLANG_HAS_ATTRIBUTE(attribute) JSON_HEDLEY_HAS_ATTRIBUTE(attribute) - -#if defined(JSON_HEDLEY_CLANG_HAS_CPP_ATTRIBUTE) - #undef JSON_HEDLEY_CLANG_HAS_CPP_ATTRIBUTE -#endif -#define JSON_HEDLEY_CLANG_HAS_CPP_ATTRIBUTE(attribute) JSON_HEDLEY_HAS_CPP_ATTRIBUTE(attribute) - -#if defined(JSON_HEDLEY_CLANG_HAS_BUILTIN) - #undef JSON_HEDLEY_CLANG_HAS_BUILTIN -#endif -#define JSON_HEDLEY_CLANG_HAS_BUILTIN(builtin) JSON_HEDLEY_HAS_BUILTIN(builtin) - -#if defined(JSON_HEDLEY_CLANG_HAS_FEATURE) - #undef JSON_HEDLEY_CLANG_HAS_FEATURE -#endif -#define JSON_HEDLEY_CLANG_HAS_FEATURE(feature) JSON_HEDLEY_HAS_FEATURE(feature) - -#if defined(JSON_HEDLEY_CLANG_HAS_EXTENSION) - #undef JSON_HEDLEY_CLANG_HAS_EXTENSION -#endif -#define JSON_HEDLEY_CLANG_HAS_EXTENSION(extension) JSON_HEDLEY_HAS_EXTENSION(extension) - -#if defined(JSON_HEDLEY_CLANG_HAS_DECLSPEC_DECLSPEC_ATTRIBUTE) - #undef JSON_HEDLEY_CLANG_HAS_DECLSPEC_DECLSPEC_ATTRIBUTE -#endif -#define JSON_HEDLEY_CLANG_HAS_DECLSPEC_ATTRIBUTE(attribute) JSON_HEDLEY_HAS_DECLSPEC_ATTRIBUTE(attribute) - -#if defined(JSON_HEDLEY_CLANG_HAS_WARNING) - #undef JSON_HEDLEY_CLANG_HAS_WARNING -#endif -#define JSON_HEDLEY_CLANG_HAS_WARNING(warning) JSON_HEDLEY_HAS_WARNING(warning) - -#endif /* !defined(JSON_HEDLEY_VERSION) || (JSON_HEDLEY_VERSION < X) */ - - -// This file contains all internal macro definitions (except those affecting ABI) -// You MUST include macro_unscope.hpp at the end of json.hpp to undef all of them - -// #include - - -// exclude unsupported compilers -#if !defined(JSON_SKIP_UNSUPPORTED_COMPILER_CHECK) - #if defined(__clang__) - #if (__clang_major__ * 10000 + __clang_minor__ * 100 + __clang_patchlevel__) < 30400 - #error "unsupported Clang version - see https://github.com/nlohmann/json#supported-compilers" - #endif - #elif defined(__GNUC__) && !(defined(__ICC) || defined(__INTEL_COMPILER)) - #if (__GNUC__ * 10000 + __GNUC_MINOR__ * 100 + __GNUC_PATCHLEVEL__) < 40800 - #error "unsupported GCC version - see https://github.com/nlohmann/json#supported-compilers" - #endif - #endif -#endif - -// C++ language standard detection -// if the user manually specified the used c++ version this is skipped -#if !defined(JSON_HAS_CPP_20) && !defined(JSON_HAS_CPP_17) && !defined(JSON_HAS_CPP_14) && !defined(JSON_HAS_CPP_11) - #if (defined(__cplusplus) && __cplusplus >= 202002L) || (defined(_MSVC_LANG) && _MSVC_LANG >= 202002L) - #define JSON_HAS_CPP_20 - #define JSON_HAS_CPP_17 - #define JSON_HAS_CPP_14 - #elif (defined(__cplusplus) && __cplusplus >= 201703L) || (defined(_HAS_CXX17) && _HAS_CXX17 == 1) // fix for issue #464 - #define JSON_HAS_CPP_17 - #define JSON_HAS_CPP_14 - #elif (defined(__cplusplus) && __cplusplus >= 201402L) || (defined(_HAS_CXX14) && _HAS_CXX14 == 1) - #define JSON_HAS_CPP_14 - #endif - // the cpp 11 flag is always specified because it is the minimal required version - #define JSON_HAS_CPP_11 -#endif - -#ifdef __has_include - #if __has_include() - #include - #endif -#endif - -#if !defined(JSON_HAS_FILESYSTEM) && !defined(JSON_HAS_EXPERIMENTAL_FILESYSTEM) - #ifdef JSON_HAS_CPP_17 - #if defined(__cpp_lib_filesystem) - #define JSON_HAS_FILESYSTEM 1 - #elif defined(__cpp_lib_experimental_filesystem) - #define JSON_HAS_EXPERIMENTAL_FILESYSTEM 1 - #elif !defined(__has_include) - #define JSON_HAS_EXPERIMENTAL_FILESYSTEM 1 - #elif __has_include() - #define JSON_HAS_FILESYSTEM 1 - #elif __has_include() - #define JSON_HAS_EXPERIMENTAL_FILESYSTEM 1 - #endif - - // std::filesystem does not work on MinGW GCC 8: https://sourceforge.net/p/mingw-w64/bugs/737/ - #if defined(__MINGW32__) && defined(__GNUC__) && __GNUC__ == 8 - #undef JSON_HAS_FILESYSTEM - #undef JSON_HAS_EXPERIMENTAL_FILESYSTEM - #endif - - // no filesystem support before GCC 8: https://en.cppreference.com/w/cpp/compiler_support - #if defined(__GNUC__) && !defined(__clang__) && __GNUC__ < 8 - #undef JSON_HAS_FILESYSTEM - #undef JSON_HAS_EXPERIMENTAL_FILESYSTEM - #endif - - // no filesystem support before Clang 7: https://en.cppreference.com/w/cpp/compiler_support - #if defined(__clang_major__) && __clang_major__ < 7 - #undef JSON_HAS_FILESYSTEM - #undef JSON_HAS_EXPERIMENTAL_FILESYSTEM - #endif - - // no filesystem support before MSVC 19.14: https://en.cppreference.com/w/cpp/compiler_support - #if defined(_MSC_VER) && _MSC_VER < 1914 - #undef JSON_HAS_FILESYSTEM - #undef JSON_HAS_EXPERIMENTAL_FILESYSTEM - #endif - - // no filesystem support before iOS 13 - #if defined(__IPHONE_OS_VERSION_MIN_REQUIRED) && __IPHONE_OS_VERSION_MIN_REQUIRED < 130000 - #undef JSON_HAS_FILESYSTEM - #undef JSON_HAS_EXPERIMENTAL_FILESYSTEM - #endif - - // no filesystem support before macOS Catalina - #if defined(__MAC_OS_X_VERSION_MIN_REQUIRED) && __MAC_OS_X_VERSION_MIN_REQUIRED < 101500 - #undef JSON_HAS_FILESYSTEM - #undef JSON_HAS_EXPERIMENTAL_FILESYSTEM - #endif - #endif -#endif - -#ifndef JSON_HAS_EXPERIMENTAL_FILESYSTEM - #define JSON_HAS_EXPERIMENTAL_FILESYSTEM 0 -#endif - -#ifndef JSON_HAS_FILESYSTEM - #define JSON_HAS_FILESYSTEM 0 -#endif - -#ifndef JSON_HAS_THREE_WAY_COMPARISON - #if defined(__cpp_impl_three_way_comparison) && __cpp_impl_three_way_comparison >= 201907L \ - && defined(__cpp_lib_three_way_comparison) && __cpp_lib_three_way_comparison >= 201907L - #define JSON_HAS_THREE_WAY_COMPARISON 1 - #else - #define JSON_HAS_THREE_WAY_COMPARISON 0 - #endif -#endif - -#ifndef JSON_HAS_RANGES - // ranges header shipping in GCC 11.1.0 (released 2021-04-27) has syntax error - #if defined(__GLIBCXX__) && __GLIBCXX__ == 20210427 - #define JSON_HAS_RANGES 0 - #elif defined(__cpp_lib_ranges) - #define JSON_HAS_RANGES 1 - #else - #define JSON_HAS_RANGES 0 - #endif -#endif - -#ifdef JSON_HAS_CPP_17 - #define JSON_INLINE_VARIABLE inline -#else - #define JSON_INLINE_VARIABLE -#endif - -#if JSON_HEDLEY_HAS_ATTRIBUTE(no_unique_address) - #define JSON_NO_UNIQUE_ADDRESS [[no_unique_address]] -#else - #define JSON_NO_UNIQUE_ADDRESS -#endif - -// disable documentation warnings on clang -#if defined(__clang__) - #pragma clang diagnostic push - #pragma clang diagnostic ignored "-Wdocumentation" - #pragma clang diagnostic ignored "-Wdocumentation-unknown-command" -#endif - -// allow disabling exceptions -#if (defined(__cpp_exceptions) || defined(__EXCEPTIONS) || defined(_CPPUNWIND)) && !defined(JSON_NOEXCEPTION) - #define JSON_THROW(exception) throw exception - #define JSON_TRY try - #define JSON_CATCH(exception) catch(exception) - #define JSON_INTERNAL_CATCH(exception) catch(exception) -#else - #include - #define JSON_THROW(exception) std::abort() - #define JSON_TRY if(true) - #define JSON_CATCH(exception) if(false) - #define JSON_INTERNAL_CATCH(exception) if(false) -#endif - -// override exception macros -#if defined(JSON_THROW_USER) - #undef JSON_THROW - #define JSON_THROW JSON_THROW_USER -#endif -#if defined(JSON_TRY_USER) - #undef JSON_TRY - #define JSON_TRY JSON_TRY_USER -#endif -#if defined(JSON_CATCH_USER) - #undef JSON_CATCH - #define JSON_CATCH JSON_CATCH_USER - #undef JSON_INTERNAL_CATCH - #define JSON_INTERNAL_CATCH JSON_CATCH_USER -#endif -#if defined(JSON_INTERNAL_CATCH_USER) - #undef JSON_INTERNAL_CATCH - #define JSON_INTERNAL_CATCH JSON_INTERNAL_CATCH_USER -#endif - -// allow overriding assert -#if !defined(JSON_ASSERT) - #include // assert - #define JSON_ASSERT(x) assert(x) -#endif - -// allow to access some private functions (needed by the test suite) -#if defined(JSON_TESTS_PRIVATE) - #define JSON_PRIVATE_UNLESS_TESTED public -#else - #define JSON_PRIVATE_UNLESS_TESTED private -#endif - -/*! -@brief macro to briefly define a mapping between an enum and JSON -@def NLOHMANN_JSON_SERIALIZE_ENUM -@since version 3.4.0 -*/ -#define NLOHMANN_JSON_SERIALIZE_ENUM(ENUM_TYPE, ...) \ - template \ - inline void to_json(BasicJsonType& j, const ENUM_TYPE& e) \ - { \ - static_assert(std::is_enum::value, #ENUM_TYPE " must be an enum!"); \ - static const std::pair m[] = __VA_ARGS__; \ - auto it = std::find_if(std::begin(m), std::end(m), \ - [e](const std::pair& ej_pair) -> bool \ - { \ - return ej_pair.first == e; \ - }); \ - j = ((it != std::end(m)) ? it : std::begin(m))->second; \ - } \ - template \ - inline void from_json(const BasicJsonType& j, ENUM_TYPE& e) \ - { \ - static_assert(std::is_enum::value, #ENUM_TYPE " must be an enum!"); \ - static const std::pair m[] = __VA_ARGS__; \ - auto it = std::find_if(std::begin(m), std::end(m), \ - [&j](const std::pair& ej_pair) -> bool \ - { \ - return ej_pair.second == j; \ - }); \ - e = ((it != std::end(m)) ? it : std::begin(m))->first; \ - } - -// Ugly macros to avoid uglier copy-paste when specializing basic_json. They -// may be removed in the future once the class is split. - -#define NLOHMANN_BASIC_JSON_TPL_DECLARATION \ - template class ObjectType, \ - template class ArrayType, \ - class StringType, class BooleanType, class NumberIntegerType, \ - class NumberUnsignedType, class NumberFloatType, \ - template class AllocatorType, \ - template class JSONSerializer, \ - class BinaryType> - -#define NLOHMANN_BASIC_JSON_TPL \ - basic_json - -// Macros to simplify conversion from/to types - -#define NLOHMANN_JSON_EXPAND( x ) x -#define NLOHMANN_JSON_GET_MACRO(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34, _35, _36, _37, _38, _39, _40, _41, _42, _43, _44, _45, _46, _47, _48, _49, _50, _51, _52, _53, _54, _55, _56, _57, _58, _59, _60, _61, _62, _63, _64, NAME,...) NAME -#define NLOHMANN_JSON_PASTE(...) NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_GET_MACRO(__VA_ARGS__, \ - NLOHMANN_JSON_PASTE64, \ - NLOHMANN_JSON_PASTE63, \ - NLOHMANN_JSON_PASTE62, \ - NLOHMANN_JSON_PASTE61, \ - NLOHMANN_JSON_PASTE60, \ - NLOHMANN_JSON_PASTE59, \ - NLOHMANN_JSON_PASTE58, \ - NLOHMANN_JSON_PASTE57, \ - NLOHMANN_JSON_PASTE56, \ - NLOHMANN_JSON_PASTE55, \ - NLOHMANN_JSON_PASTE54, \ - NLOHMANN_JSON_PASTE53, \ - NLOHMANN_JSON_PASTE52, \ - NLOHMANN_JSON_PASTE51, \ - NLOHMANN_JSON_PASTE50, \ - NLOHMANN_JSON_PASTE49, \ - NLOHMANN_JSON_PASTE48, \ - NLOHMANN_JSON_PASTE47, \ - NLOHMANN_JSON_PASTE46, \ - NLOHMANN_JSON_PASTE45, \ - NLOHMANN_JSON_PASTE44, \ - NLOHMANN_JSON_PASTE43, \ - NLOHMANN_JSON_PASTE42, \ - NLOHMANN_JSON_PASTE41, \ - NLOHMANN_JSON_PASTE40, \ - NLOHMANN_JSON_PASTE39, \ - NLOHMANN_JSON_PASTE38, \ - NLOHMANN_JSON_PASTE37, \ - NLOHMANN_JSON_PASTE36, \ - NLOHMANN_JSON_PASTE35, \ - NLOHMANN_JSON_PASTE34, \ - NLOHMANN_JSON_PASTE33, \ - NLOHMANN_JSON_PASTE32, \ - NLOHMANN_JSON_PASTE31, \ - NLOHMANN_JSON_PASTE30, \ - NLOHMANN_JSON_PASTE29, \ - NLOHMANN_JSON_PASTE28, \ - NLOHMANN_JSON_PASTE27, \ - NLOHMANN_JSON_PASTE26, \ - NLOHMANN_JSON_PASTE25, \ - NLOHMANN_JSON_PASTE24, \ - NLOHMANN_JSON_PASTE23, \ - NLOHMANN_JSON_PASTE22, \ - NLOHMANN_JSON_PASTE21, \ - NLOHMANN_JSON_PASTE20, \ - NLOHMANN_JSON_PASTE19, \ - NLOHMANN_JSON_PASTE18, \ - NLOHMANN_JSON_PASTE17, \ - NLOHMANN_JSON_PASTE16, \ - NLOHMANN_JSON_PASTE15, \ - NLOHMANN_JSON_PASTE14, \ - NLOHMANN_JSON_PASTE13, \ - NLOHMANN_JSON_PASTE12, \ - NLOHMANN_JSON_PASTE11, \ - NLOHMANN_JSON_PASTE10, \ - NLOHMANN_JSON_PASTE9, \ - NLOHMANN_JSON_PASTE8, \ - NLOHMANN_JSON_PASTE7, \ - NLOHMANN_JSON_PASTE6, \ - NLOHMANN_JSON_PASTE5, \ - NLOHMANN_JSON_PASTE4, \ - NLOHMANN_JSON_PASTE3, \ - NLOHMANN_JSON_PASTE2, \ - NLOHMANN_JSON_PASTE1)(__VA_ARGS__)) -#define NLOHMANN_JSON_PASTE2(func, v1) func(v1) -#define NLOHMANN_JSON_PASTE3(func, v1, v2) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE2(func, v2) -#define NLOHMANN_JSON_PASTE4(func, v1, v2, v3) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE3(func, v2, v3) -#define NLOHMANN_JSON_PASTE5(func, v1, v2, v3, v4) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE4(func, v2, v3, v4) -#define NLOHMANN_JSON_PASTE6(func, v1, v2, v3, v4, v5) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE5(func, v2, v3, v4, v5) -#define NLOHMANN_JSON_PASTE7(func, v1, v2, v3, v4, v5, v6) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE6(func, v2, v3, v4, v5, v6) -#define NLOHMANN_JSON_PASTE8(func, v1, v2, v3, v4, v5, v6, v7) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE7(func, v2, v3, v4, v5, v6, v7) -#define NLOHMANN_JSON_PASTE9(func, v1, v2, v3, v4, v5, v6, v7, v8) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE8(func, v2, v3, v4, v5, v6, v7, v8) -#define NLOHMANN_JSON_PASTE10(func, v1, v2, v3, v4, v5, v6, v7, v8, v9) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE9(func, v2, v3, v4, v5, v6, v7, v8, v9) -#define NLOHMANN_JSON_PASTE11(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE10(func, v2, v3, v4, v5, v6, v7, v8, v9, v10) -#define NLOHMANN_JSON_PASTE12(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE11(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11) -#define NLOHMANN_JSON_PASTE13(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE12(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12) -#define NLOHMANN_JSON_PASTE14(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE13(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13) -#define NLOHMANN_JSON_PASTE15(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE14(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14) -#define NLOHMANN_JSON_PASTE16(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE15(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15) -#define NLOHMANN_JSON_PASTE17(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE16(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16) -#define NLOHMANN_JSON_PASTE18(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE17(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17) -#define NLOHMANN_JSON_PASTE19(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE18(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18) -#define NLOHMANN_JSON_PASTE20(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE19(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19) -#define NLOHMANN_JSON_PASTE21(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE20(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20) -#define NLOHMANN_JSON_PASTE22(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE21(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21) -#define NLOHMANN_JSON_PASTE23(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE22(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22) -#define NLOHMANN_JSON_PASTE24(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE23(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23) -#define NLOHMANN_JSON_PASTE25(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE24(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24) -#define NLOHMANN_JSON_PASTE26(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE25(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25) -#define NLOHMANN_JSON_PASTE27(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE26(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26) -#define NLOHMANN_JSON_PASTE28(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE27(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27) -#define NLOHMANN_JSON_PASTE29(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE28(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28) -#define NLOHMANN_JSON_PASTE30(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE29(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29) -#define NLOHMANN_JSON_PASTE31(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE30(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30) -#define NLOHMANN_JSON_PASTE32(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE31(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31) -#define NLOHMANN_JSON_PASTE33(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE32(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32) -#define NLOHMANN_JSON_PASTE34(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE33(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33) -#define NLOHMANN_JSON_PASTE35(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE34(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34) -#define NLOHMANN_JSON_PASTE36(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE35(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35) -#define NLOHMANN_JSON_PASTE37(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE36(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36) -#define NLOHMANN_JSON_PASTE38(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE37(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37) -#define NLOHMANN_JSON_PASTE39(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE38(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38) -#define NLOHMANN_JSON_PASTE40(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE39(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39) -#define NLOHMANN_JSON_PASTE41(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE40(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40) -#define NLOHMANN_JSON_PASTE42(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE41(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41) -#define NLOHMANN_JSON_PASTE43(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE42(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42) -#define NLOHMANN_JSON_PASTE44(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE43(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43) -#define NLOHMANN_JSON_PASTE45(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE44(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44) -#define NLOHMANN_JSON_PASTE46(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE45(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45) -#define NLOHMANN_JSON_PASTE47(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE46(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46) -#define NLOHMANN_JSON_PASTE48(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE47(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47) -#define NLOHMANN_JSON_PASTE49(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE48(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48) -#define NLOHMANN_JSON_PASTE50(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE49(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49) -#define NLOHMANN_JSON_PASTE51(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE50(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50) -#define NLOHMANN_JSON_PASTE52(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE51(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51) -#define NLOHMANN_JSON_PASTE53(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE52(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52) -#define NLOHMANN_JSON_PASTE54(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE53(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53) -#define NLOHMANN_JSON_PASTE55(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE54(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54) -#define NLOHMANN_JSON_PASTE56(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE55(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55) -#define NLOHMANN_JSON_PASTE57(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE56(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56) -#define NLOHMANN_JSON_PASTE58(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE57(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57) -#define NLOHMANN_JSON_PASTE59(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE58(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58) -#define NLOHMANN_JSON_PASTE60(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE59(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59) -#define NLOHMANN_JSON_PASTE61(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE60(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60) -#define NLOHMANN_JSON_PASTE62(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60, v61) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE61(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60, v61) -#define NLOHMANN_JSON_PASTE63(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60, v61, v62) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE62(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60, v61, v62) -#define NLOHMANN_JSON_PASTE64(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60, v61, v62, v63) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE63(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60, v61, v62, v63) - -#define NLOHMANN_JSON_TO(v1) nlohmann_json_j[#v1] = nlohmann_json_t.v1; -#define NLOHMANN_JSON_FROM(v1) nlohmann_json_j.at(#v1).get_to(nlohmann_json_t.v1); -#define NLOHMANN_JSON_FROM_WITH_DEFAULT(v1) nlohmann_json_t.v1 = nlohmann_json_j.value(#v1, nlohmann_json_default_obj.v1); - -/*! -@brief macro -@def NLOHMANN_DEFINE_TYPE_INTRUSIVE -@since version 3.9.0 -*/ -#define NLOHMANN_DEFINE_TYPE_INTRUSIVE(Type, ...) \ - friend void to_json(nlohmann::json& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \ - friend void from_json(const nlohmann::json& nlohmann_json_j, Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM, __VA_ARGS__)) } - -#define NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(Type, ...) \ - friend void to_json(nlohmann::json& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \ - friend void from_json(const nlohmann::json& nlohmann_json_j, Type& nlohmann_json_t) { Type nlohmann_json_default_obj; NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM_WITH_DEFAULT, __VA_ARGS__)) } - -/*! -@brief macro -@def NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE -@since version 3.9.0 -*/ -#define NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(Type, ...) \ - inline void to_json(nlohmann::json& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \ - inline void from_json(const nlohmann::json& nlohmann_json_j, Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM, __VA_ARGS__)) } - -#define NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT(Type, ...) \ - inline void to_json(nlohmann::json& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \ - inline void from_json(const nlohmann::json& nlohmann_json_j, Type& nlohmann_json_t) { Type nlohmann_json_default_obj; NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM_WITH_DEFAULT, __VA_ARGS__)) } - - -// inspired from https://stackoverflow.com/a/26745591 -// allows to call any std function as if (e.g. with begin): -// using std::begin; begin(x); -// -// it allows using the detected idiom to retrieve the return type -// of such an expression -#define NLOHMANN_CAN_CALL_STD_FUNC_IMPL(std_name) \ - namespace detail { \ - using std::std_name; \ - \ - template \ - using result_of_##std_name = decltype(std_name(std::declval()...)); \ - } \ - \ - namespace detail2 { \ - struct std_name##_tag \ - { \ - }; \ - \ - template \ - std_name##_tag std_name(T&&...); \ - \ - template \ - using result_of_##std_name = decltype(std_name(std::declval()...)); \ - \ - template \ - struct would_call_std_##std_name \ - { \ - static constexpr auto const value = ::nlohmann::detail:: \ - is_detected_exact::value; \ - }; \ - } /* namespace detail2 */ \ - \ - template \ - struct would_call_std_##std_name : detail2::would_call_std_##std_name \ - { \ - } - -#ifndef JSON_USE_IMPLICIT_CONVERSIONS - #define JSON_USE_IMPLICIT_CONVERSIONS 1 -#endif - -#if JSON_USE_IMPLICIT_CONVERSIONS - #define JSON_EXPLICIT -#else - #define JSON_EXPLICIT explicit -#endif - -#ifndef JSON_DISABLE_ENUM_SERIALIZATION - #define JSON_DISABLE_ENUM_SERIALIZATION 0 -#endif - -#ifndef JSON_USE_GLOBAL_UDLS - #define JSON_USE_GLOBAL_UDLS 1 -#endif - -#if JSON_HAS_THREE_WAY_COMPARISON - #include // partial_ordering -#endif - -NLOHMANN_JSON_NAMESPACE_BEGIN -namespace detail -{ - -/////////////////////////// -// JSON type enumeration // -/////////////////////////// - -/*! -@brief the JSON type enumeration - -This enumeration collects the different JSON types. It is internally used to -distinguish the stored values, and the functions @ref basic_json::is_null(), -@ref basic_json::is_object(), @ref basic_json::is_array(), -@ref basic_json::is_string(), @ref basic_json::is_boolean(), -@ref basic_json::is_number() (with @ref basic_json::is_number_integer(), -@ref basic_json::is_number_unsigned(), and @ref basic_json::is_number_float()), -@ref basic_json::is_discarded(), @ref basic_json::is_primitive(), and -@ref basic_json::is_structured() rely on it. - -@note There are three enumeration entries (number_integer, number_unsigned, and -number_float), because the library distinguishes these three types for numbers: -@ref basic_json::number_unsigned_t is used for unsigned integers, -@ref basic_json::number_integer_t is used for signed integers, and -@ref basic_json::number_float_t is used for floating-point numbers or to -approximate integers which do not fit in the limits of their respective type. - -@sa see @ref basic_json::basic_json(const value_t value_type) -- create a JSON -value with the default value for a given type - -@since version 1.0.0 -*/ -enum class value_t : std::uint8_t -{ - null, ///< null value - object, ///< object (unordered set of name/value pairs) - array, ///< array (ordered collection of values) - string, ///< string value - boolean, ///< boolean value - number_integer, ///< number value (signed integer) - number_unsigned, ///< number value (unsigned integer) - number_float, ///< number value (floating-point) - binary, ///< binary array (ordered collection of bytes) - discarded ///< discarded by the parser callback function -}; - -/*! -@brief comparison operator for JSON types - -Returns an ordering that is similar to Python: -- order: null < boolean < number < object < array < string < binary -- furthermore, each type is not smaller than itself -- discarded values are not comparable -- binary is represented as a b"" string in python and directly comparable to a - string; however, making a binary array directly comparable with a string would - be surprising behavior in a JSON file. - -@since version 1.0.0 -*/ -#if JSON_HAS_THREE_WAY_COMPARISON - inline std::partial_ordering operator<=>(const value_t lhs, const value_t rhs) noexcept // *NOPAD* -#else - inline bool operator<(const value_t lhs, const value_t rhs) noexcept -#endif -{ - static constexpr std::array order = {{ - 0 /* null */, 3 /* object */, 4 /* array */, 5 /* string */, - 1 /* boolean */, 2 /* integer */, 2 /* unsigned */, 2 /* float */, - 6 /* binary */ - } - }; - - const auto l_index = static_cast(lhs); - const auto r_index = static_cast(rhs); -#if JSON_HAS_THREE_WAY_COMPARISON - if (l_index < order.size() && r_index < order.size()) - { - return order[l_index] <=> order[r_index]; // *NOPAD* - } - return std::partial_ordering::unordered; -#else - return l_index < order.size() && r_index < order.size() && order[l_index] < order[r_index]; -#endif -} - -// GCC selects the built-in operator< over an operator rewritten from -// a user-defined spaceship operator -// Clang, MSVC, and ICC select the rewritten candidate -// (see GCC bug https://gcc.gnu.org/bugzilla/show_bug.cgi?id=105200) -#if JSON_HAS_THREE_WAY_COMPARISON && defined(__GNUC__) -inline bool operator<(const value_t lhs, const value_t rhs) noexcept -{ - return std::is_lt(lhs <=> rhs); // *NOPAD* -} -#endif - -} // namespace detail -NLOHMANN_JSON_NAMESPACE_END - -// #include -// __ _____ _____ _____ -// __| | __| | | | JSON for Modern C++ -// | | |__ | | | | | | version 3.11.2 -// |_____|_____|_____|_|___| https://github.com/nlohmann/json -// -// SPDX-FileCopyrightText: 2013-2022 Niels Lohmann -// SPDX-License-Identifier: MIT - - - -// #include - - -NLOHMANN_JSON_NAMESPACE_BEGIN -namespace detail -{ - -/*! -@brief replace all occurrences of a substring by another string - -@param[in,out] s the string to manipulate; changed so that all - occurrences of @a f are replaced with @a t -@param[in] f the substring to replace with @a t -@param[in] t the string to replace @a f - -@pre The search string @a f must not be empty. **This precondition is -enforced with an assertion.** - -@since version 2.0.0 -*/ -template -inline void replace_substring(StringType& s, const StringType& f, - const StringType& t) -{ - JSON_ASSERT(!f.empty()); - for (auto pos = s.find(f); // find first occurrence of f - pos != StringType::npos; // make sure f was found - s.replace(pos, f.size(), t), // replace with t, and - pos = s.find(f, pos + t.size())) // find next occurrence of f - {} -} - -/*! - * @brief string escaping as described in RFC 6901 (Sect. 4) - * @param[in] s string to escape - * @return escaped string - * - * Note the order of escaping "~" to "~0" and "/" to "~1" is important. - */ -template -inline StringType escape(StringType s) -{ - replace_substring(s, StringType{"~"}, StringType{"~0"}); - replace_substring(s, StringType{"/"}, StringType{"~1"}); - return s; -} - -/*! - * @brief string unescaping as described in RFC 6901 (Sect. 4) - * @param[in] s string to unescape - * @return unescaped string - * - * Note the order of escaping "~1" to "/" and "~0" to "~" is important. - */ -template -static void unescape(StringType& s) -{ - replace_substring(s, StringType{"~1"}, StringType{"/"}); - replace_substring(s, StringType{"~0"}, StringType{"~"}); -} - -} // namespace detail -NLOHMANN_JSON_NAMESPACE_END - -// #include -// __ _____ _____ _____ -// __| | __| | | | JSON for Modern C++ -// | | |__ | | | | | | version 3.11.2 -// |_____|_____|_____|_|___| https://github.com/nlohmann/json -// -// SPDX-FileCopyrightText: 2013-2022 Niels Lohmann -// SPDX-License-Identifier: MIT - - - -#include // size_t - -// #include - - -NLOHMANN_JSON_NAMESPACE_BEGIN -namespace detail -{ - -/// struct to capture the start position of the current token -struct position_t -{ - /// the total number of characters read - std::size_t chars_read_total = 0; - /// the number of characters read in the current line - std::size_t chars_read_current_line = 0; - /// the number of lines read - std::size_t lines_read = 0; - - /// conversion to size_t to preserve SAX interface - constexpr operator size_t() const - { - return chars_read_total; - } -}; - -} // namespace detail -NLOHMANN_JSON_NAMESPACE_END - -// #include - -// #include -// __ _____ _____ _____ -// __| | __| | | | JSON for Modern C++ -// | | |__ | | | | | | version 3.11.2 -// |_____|_____|_____|_|___| https://github.com/nlohmann/json -// -// SPDX-FileCopyrightText: 2013-2022 Niels Lohmann -// SPDX-FileCopyrightText: 2018 The Abseil Authors -// SPDX-License-Identifier: MIT - - - -#include // array -#include // size_t -#include // conditional, enable_if, false_type, integral_constant, is_constructible, is_integral, is_same, remove_cv, remove_reference, true_type -#include // index_sequence, make_index_sequence, index_sequence_for - -// #include - - -NLOHMANN_JSON_NAMESPACE_BEGIN -namespace detail -{ - -template -using uncvref_t = typename std::remove_cv::type>::type; - -#ifdef JSON_HAS_CPP_14 - -// the following utilities are natively available in C++14 -using std::enable_if_t; -using std::index_sequence; -using std::make_index_sequence; -using std::index_sequence_for; - -#else - -// alias templates to reduce boilerplate -template -using enable_if_t = typename std::enable_if::type; - -// The following code is taken from https://github.com/abseil/abseil-cpp/blob/10cb35e459f5ecca5b2ff107635da0bfa41011b4/absl/utility/utility.h -// which is part of Google Abseil (https://github.com/abseil/abseil-cpp), licensed under the Apache License 2.0. - -//// START OF CODE FROM GOOGLE ABSEIL - -// integer_sequence -// -// Class template representing a compile-time integer sequence. An instantiation -// of `integer_sequence` has a sequence of integers encoded in its -// type through its template arguments (which is a common need when -// working with C++11 variadic templates). `absl::integer_sequence` is designed -// to be a drop-in replacement for C++14's `std::integer_sequence`. -// -// Example: -// -// template< class T, T... Ints > -// void user_function(integer_sequence); -// -// int main() -// { -// // user_function's `T` will be deduced to `int` and `Ints...` -// // will be deduced to `0, 1, 2, 3, 4`. -// user_function(make_integer_sequence()); -// } -template -struct integer_sequence -{ - using value_type = T; - static constexpr std::size_t size() noexcept - { - return sizeof...(Ints); - } -}; - -// index_sequence -// -// A helper template for an `integer_sequence` of `size_t`, -// `absl::index_sequence` is designed to be a drop-in replacement for C++14's -// `std::index_sequence`. -template -using index_sequence = integer_sequence; - -namespace utility_internal -{ - -template -struct Extend; - -// Note that SeqSize == sizeof...(Ints). It's passed explicitly for efficiency. -template -struct Extend, SeqSize, 0> -{ - using type = integer_sequence < T, Ints..., (Ints + SeqSize)... >; -}; - -template -struct Extend, SeqSize, 1> -{ - using type = integer_sequence < T, Ints..., (Ints + SeqSize)..., 2 * SeqSize >; -}; - -// Recursion helper for 'make_integer_sequence'. -// 'Gen::type' is an alias for 'integer_sequence'. -template -struct Gen -{ - using type = - typename Extend < typename Gen < T, N / 2 >::type, N / 2, N % 2 >::type; -}; - -template -struct Gen -{ - using type = integer_sequence; -}; - -} // namespace utility_internal - -// Compile-time sequences of integers - -// make_integer_sequence -// -// This template alias is equivalent to -// `integer_sequence`, and is designed to be a drop-in -// replacement for C++14's `std::make_integer_sequence`. -template -using make_integer_sequence = typename utility_internal::Gen::type; - -// make_index_sequence -// -// This template alias is equivalent to `index_sequence<0, 1, ..., N-1>`, -// and is designed to be a drop-in replacement for C++14's -// `std::make_index_sequence`. -template -using make_index_sequence = make_integer_sequence; - -// index_sequence_for -// -// Converts a typename pack into an index sequence of the same length, and -// is designed to be a drop-in replacement for C++14's -// `std::index_sequence_for()` -template -using index_sequence_for = make_index_sequence; - -//// END OF CODE FROM GOOGLE ABSEIL - -#endif - -// dispatch utility (taken from ranges-v3) -template struct priority_tag : priority_tag < N - 1 > {}; -template<> struct priority_tag<0> {}; - -// taken from ranges-v3 -template -struct static_const -{ - static JSON_INLINE_VARIABLE constexpr T value{}; -}; - -#ifndef JSON_HAS_CPP_17 - template - constexpr T static_const::value; -#endif - -template -inline constexpr std::array make_array(Args&& ... args) -{ - return std::array {{static_cast(std::forward(args))...}}; -} - -} // namespace detail -NLOHMANN_JSON_NAMESPACE_END - -// #include -// __ _____ _____ _____ -// __| | __| | | | JSON for Modern C++ -// | | |__ | | | | | | version 3.11.2 -// |_____|_____|_____|_|___| https://github.com/nlohmann/json -// -// SPDX-FileCopyrightText: 2013-2022 Niels Lohmann -// SPDX-License-Identifier: MIT - - - -#include // numeric_limits -#include // false_type, is_constructible, is_integral, is_same, true_type -#include // declval -#include // tuple - -// #include -// __ _____ _____ _____ -// __| | __| | | | JSON for Modern C++ -// | | |__ | | | | | | version 3.11.2 -// |_____|_____|_____|_|___| https://github.com/nlohmann/json -// -// SPDX-FileCopyrightText: 2013-2022 Niels Lohmann -// SPDX-License-Identifier: MIT - - - -#include // random_access_iterator_tag - -// #include - -// #include - -// #include - - -NLOHMANN_JSON_NAMESPACE_BEGIN -namespace detail -{ - -template -struct iterator_types {}; - -template -struct iterator_types < - It, - void_t> -{ - using difference_type = typename It::difference_type; - using value_type = typename It::value_type; - using pointer = typename It::pointer; - using reference = typename It::reference; - using iterator_category = typename It::iterator_category; -}; - -// This is required as some compilers implement std::iterator_traits in a way that -// doesn't work with SFINAE. See https://github.com/nlohmann/json/issues/1341. -template -struct iterator_traits -{ -}; - -template -struct iterator_traits < T, enable_if_t < !std::is_pointer::value >> - : iterator_types -{ -}; - -template -struct iterator_traits::value>> -{ - using iterator_category = std::random_access_iterator_tag; - using value_type = T; - using difference_type = ptrdiff_t; - using pointer = T*; - using reference = T&; -}; - -} // namespace detail -NLOHMANN_JSON_NAMESPACE_END - -// #include - -// #include -// __ _____ _____ _____ -// __| | __| | | | JSON for Modern C++ -// | | |__ | | | | | | version 3.11.2 -// |_____|_____|_____|_|___| https://github.com/nlohmann/json -// -// SPDX-FileCopyrightText: 2013-2022 Niels Lohmann -// SPDX-License-Identifier: MIT - - - -// #include - - -NLOHMANN_JSON_NAMESPACE_BEGIN - -NLOHMANN_CAN_CALL_STD_FUNC_IMPL(begin); - -NLOHMANN_JSON_NAMESPACE_END - -// #include -// __ _____ _____ _____ -// __| | __| | | | JSON for Modern C++ -// | | |__ | | | | | | version 3.11.2 -// |_____|_____|_____|_|___| https://github.com/nlohmann/json -// -// SPDX-FileCopyrightText: 2013-2022 Niels Lohmann -// SPDX-License-Identifier: MIT - - - -// #include - - -NLOHMANN_JSON_NAMESPACE_BEGIN - -NLOHMANN_CAN_CALL_STD_FUNC_IMPL(end); - -NLOHMANN_JSON_NAMESPACE_END - -// #include - -// #include - -// #include -// __ _____ _____ _____ -// __| | __| | | | JSON for Modern C++ -// | | |__ | | | | | | version 3.11.2 -// |_____|_____|_____|_|___| https://github.com/nlohmann/json -// -// SPDX-FileCopyrightText: 2013-2022 Niels Lohmann -// SPDX-License-Identifier: MIT - -#ifndef INCLUDE_NLOHMANN_JSON_FWD_HPP_ - #define INCLUDE_NLOHMANN_JSON_FWD_HPP_ - - #include // int64_t, uint64_t - #include // map - #include // allocator - #include // string - #include // vector - - // #include - - - /*! - @brief namespace for Niels Lohmann - @see https://github.com/nlohmann - @since version 1.0.0 - */ - NLOHMANN_JSON_NAMESPACE_BEGIN - - /*! - @brief default JSONSerializer template argument - - This serializer ignores the template arguments and uses ADL - ([argument-dependent lookup](https://en.cppreference.com/w/cpp/language/adl)) - for serialization. - */ - template - struct adl_serializer; - - /// a class to store JSON values - /// @sa https://json.nlohmann.me/api/basic_json/ - template class ObjectType = - std::map, - template class ArrayType = std::vector, - class StringType = std::string, class BooleanType = bool, - class NumberIntegerType = std::int64_t, - class NumberUnsignedType = std::uint64_t, - class NumberFloatType = double, - template class AllocatorType = std::allocator, - template class JSONSerializer = - adl_serializer, - class BinaryType = std::vector> - class basic_json; - - /// @brief JSON Pointer defines a string syntax for identifying a specific value within a JSON document - /// @sa https://json.nlohmann.me/api/json_pointer/ - template - class json_pointer; - - /*! - @brief default specialization - @sa https://json.nlohmann.me/api/json/ - */ - using json = basic_json<>; - - /// @brief a minimal map-like container that preserves insertion order - /// @sa https://json.nlohmann.me/api/ordered_map/ - template - struct ordered_map; - - /// @brief specialization that maintains the insertion order of object keys - /// @sa https://json.nlohmann.me/api/ordered_json/ - using ordered_json = basic_json; - - NLOHMANN_JSON_NAMESPACE_END - -#endif // INCLUDE_NLOHMANN_JSON_FWD_HPP_ - - -NLOHMANN_JSON_NAMESPACE_BEGIN -/*! -@brief detail namespace with internal helper functions - -This namespace collects functions that should not be exposed, -implementations of some @ref basic_json methods, and meta-programming helpers. - -@since version 2.1.0 -*/ -namespace detail -{ - -///////////// -// helpers // -///////////// - -// Note to maintainers: -// -// Every trait in this file expects a non CV-qualified type. -// The only exceptions are in the 'aliases for detected' section -// (i.e. those of the form: decltype(T::member_function(std::declval()))) -// -// In this case, T has to be properly CV-qualified to constraint the function arguments -// (e.g. to_json(BasicJsonType&, const T&)) - -template struct is_basic_json : std::false_type {}; - -NLOHMANN_BASIC_JSON_TPL_DECLARATION -struct is_basic_json : std::true_type {}; - -// used by exceptions create() member functions -// true_type for pointer to possibly cv-qualified basic_json or std::nullptr_t -// false_type otherwise -template -struct is_basic_json_context : - std::integral_constant < bool, - is_basic_json::type>::type>::value - || std::is_same::value > -{}; - -////////////////////// -// json_ref helpers // -////////////////////// - -template -class json_ref; - -template -struct is_json_ref : std::false_type {}; - -template -struct is_json_ref> : std::true_type {}; - -////////////////////////// -// aliases for detected // -////////////////////////// - -template -using mapped_type_t = typename T::mapped_type; - -template -using key_type_t = typename T::key_type; - -template -using value_type_t = typename T::value_type; - -template -using difference_type_t = typename T::difference_type; - -template -using pointer_t = typename T::pointer; - -template -using reference_t = typename T::reference; - -template -using iterator_category_t = typename T::iterator_category; - -template -using to_json_function = decltype(T::to_json(std::declval()...)); - -template -using from_json_function = decltype(T::from_json(std::declval()...)); - -template -using get_template_function = decltype(std::declval().template get()); - -// trait checking if JSONSerializer::from_json(json const&, udt&) exists -template -struct has_from_json : std::false_type {}; - -// trait checking if j.get is valid -// use this trait instead of std::is_constructible or std::is_convertible, -// both rely on, or make use of implicit conversions, and thus fail when T -// has several constructors/operator= (see https://github.com/nlohmann/json/issues/958) -template -struct is_getable -{ - static constexpr bool value = is_detected::value; -}; - -template -struct has_from_json < BasicJsonType, T, enable_if_t < !is_basic_json::value >> -{ - using serializer = typename BasicJsonType::template json_serializer; - - static constexpr bool value = - is_detected_exact::value; -}; - -// This trait checks if JSONSerializer::from_json(json const&) exists -// this overload is used for non-default-constructible user-defined-types -template -struct has_non_default_from_json : std::false_type {}; - -template -struct has_non_default_from_json < BasicJsonType, T, enable_if_t < !is_basic_json::value >> -{ - using serializer = typename BasicJsonType::template json_serializer; - - static constexpr bool value = - is_detected_exact::value; -}; - -// This trait checks if BasicJsonType::json_serializer::to_json exists -// Do not evaluate the trait when T is a basic_json type, to avoid template instantiation infinite recursion. -template -struct has_to_json : std::false_type {}; - -template -struct has_to_json < BasicJsonType, T, enable_if_t < !is_basic_json::value >> -{ - using serializer = typename BasicJsonType::template json_serializer; - - static constexpr bool value = - is_detected_exact::value; -}; - -template -using detect_key_compare = typename T::key_compare; - -template -struct has_key_compare : std::integral_constant::value> {}; - -// obtains the actual object key comparator -template -struct actual_object_comparator -{ - using object_t = typename BasicJsonType::object_t; - using object_comparator_t = typename BasicJsonType::default_object_comparator_t; - using type = typename std::conditional < has_key_compare::value, - typename object_t::key_compare, object_comparator_t>::type; -}; - -template -using actual_object_comparator_t = typename actual_object_comparator::type; - -/////////////////// -// is_ functions // -/////////////////// - -// https://en.cppreference.com/w/cpp/types/conjunction -template struct conjunction : std::true_type { }; -template struct conjunction : B { }; -template -struct conjunction -: std::conditional(B::value), conjunction, B>::type {}; - -// https://en.cppreference.com/w/cpp/types/negation -template struct negation : std::integral_constant < bool, !B::value > { }; - -// Reimplementation of is_constructible and is_default_constructible, due to them being broken for -// std::pair and std::tuple until LWG 2367 fix (see https://cplusplus.github.io/LWG/lwg-defects.html#2367). -// This causes compile errors in e.g. clang 3.5 or gcc 4.9. -template -struct is_default_constructible : std::is_default_constructible {}; - -template -struct is_default_constructible> - : conjunction, is_default_constructible> {}; - -template -struct is_default_constructible> - : conjunction, is_default_constructible> {}; - -template -struct is_default_constructible> - : conjunction...> {}; - -template -struct is_default_constructible> - : conjunction...> {}; - - -template -struct is_constructible : std::is_constructible {}; - -template -struct is_constructible> : is_default_constructible> {}; - -template -struct is_constructible> : is_default_constructible> {}; - -template -struct is_constructible> : is_default_constructible> {}; - -template -struct is_constructible> : is_default_constructible> {}; - - -template -struct is_iterator_traits : std::false_type {}; - -template -struct is_iterator_traits> -{ - private: - using traits = iterator_traits; - - public: - static constexpr auto value = - is_detected::value && - is_detected::value && - is_detected::value && - is_detected::value && - is_detected::value; -}; - -template -struct is_range -{ - private: - using t_ref = typename std::add_lvalue_reference::type; - - using iterator = detected_t; - using sentinel = detected_t; - - // to be 100% correct, it should use https://en.cppreference.com/w/cpp/iterator/input_or_output_iterator - // and https://en.cppreference.com/w/cpp/iterator/sentinel_for - // but reimplementing these would be too much work, as a lot of other concepts are used underneath - static constexpr auto is_iterator_begin = - is_iterator_traits>::value; - - public: - static constexpr bool value = !std::is_same::value && !std::is_same::value && is_iterator_begin; -}; - -template -using iterator_t = enable_if_t::value, result_of_begin())>>; - -template -using range_value_t = value_type_t>>; - -// The following implementation of is_complete_type is taken from -// https://blogs.msdn.microsoft.com/vcblog/2015/12/02/partial-support-for-expression-sfinae-in-vs-2015-update-1/ -// and is written by Xiang Fan who agreed to using it in this library. - -template -struct is_complete_type : std::false_type {}; - -template -struct is_complete_type : std::true_type {}; - -template -struct is_compatible_object_type_impl : std::false_type {}; - -template -struct is_compatible_object_type_impl < - BasicJsonType, CompatibleObjectType, - enable_if_t < is_detected::value&& - is_detected::value >> -{ - using object_t = typename BasicJsonType::object_t; - - // macOS's is_constructible does not play well with nonesuch... - static constexpr bool value = - is_constructible::value && - is_constructible::value; -}; - -template -struct is_compatible_object_type - : is_compatible_object_type_impl {}; - -template -struct is_constructible_object_type_impl : std::false_type {}; - -template -struct is_constructible_object_type_impl < - BasicJsonType, ConstructibleObjectType, - enable_if_t < is_detected::value&& - is_detected::value >> -{ - using object_t = typename BasicJsonType::object_t; - - static constexpr bool value = - (is_default_constructible::value && - (std::is_move_assignable::value || - std::is_copy_assignable::value) && - (is_constructible::value && - std::is_same < - typename object_t::mapped_type, - typename ConstructibleObjectType::mapped_type >::value)) || - (has_from_json::value || - has_non_default_from_json < - BasicJsonType, - typename ConstructibleObjectType::mapped_type >::value); -}; - -template -struct is_constructible_object_type - : is_constructible_object_type_impl {}; - -template -struct is_compatible_string_type -{ - static constexpr auto value = - is_constructible::value; -}; - -template -struct is_constructible_string_type -{ - // launder type through decltype() to fix compilation failure on ICPC -#ifdef __INTEL_COMPILER - using laundered_type = decltype(std::declval()); -#else - using laundered_type = ConstructibleStringType; -#endif - - static constexpr auto value = - conjunction < - is_constructible, - is_detected_exact>::value; -}; - -template -struct is_compatible_array_type_impl : std::false_type {}; - -template -struct is_compatible_array_type_impl < - BasicJsonType, CompatibleArrayType, - enable_if_t < - is_detected::value&& - is_iterator_traits>>::value&& -// special case for types like std::filesystem::path whose iterator's value_type are themselves -// c.f. https://github.com/nlohmann/json/pull/3073 - !std::is_same>::value >> -{ - static constexpr bool value = - is_constructible>::value; -}; - -template -struct is_compatible_array_type - : is_compatible_array_type_impl {}; - -template -struct is_constructible_array_type_impl : std::false_type {}; - -template -struct is_constructible_array_type_impl < - BasicJsonType, ConstructibleArrayType, - enable_if_t::value >> - : std::true_type {}; - -template -struct is_constructible_array_type_impl < - BasicJsonType, ConstructibleArrayType, - enable_if_t < !std::is_same::value&& - !is_compatible_string_type::value&& - is_default_constructible::value&& -(std::is_move_assignable::value || - std::is_copy_assignable::value)&& -is_detected::value&& -is_iterator_traits>>::value&& -is_detected::value&& -// special case for types like std::filesystem::path whose iterator's value_type are themselves -// c.f. https://github.com/nlohmann/json/pull/3073 -!std::is_same>::value&& - is_complete_type < - detected_t>::value >> -{ - using value_type = range_value_t; - - static constexpr bool value = - std::is_same::value || - has_from_json::value || - has_non_default_from_json < - BasicJsonType, - value_type >::value; -}; - -template -struct is_constructible_array_type - : is_constructible_array_type_impl {}; - -template -struct is_compatible_integer_type_impl : std::false_type {}; - -template -struct is_compatible_integer_type_impl < - RealIntegerType, CompatibleNumberIntegerType, - enable_if_t < std::is_integral::value&& - std::is_integral::value&& - !std::is_same::value >> -{ - // is there an assert somewhere on overflows? - using RealLimits = std::numeric_limits; - using CompatibleLimits = std::numeric_limits; - - static constexpr auto value = - is_constructible::value && - CompatibleLimits::is_integer && - RealLimits::is_signed == CompatibleLimits::is_signed; -}; - -template -struct is_compatible_integer_type - : is_compatible_integer_type_impl {}; - -template -struct is_compatible_type_impl: std::false_type {}; - -template -struct is_compatible_type_impl < - BasicJsonType, CompatibleType, - enable_if_t::value >> -{ - static constexpr bool value = - has_to_json::value; -}; - -template -struct is_compatible_type - : is_compatible_type_impl {}; - -template -struct is_constructible_tuple : std::false_type {}; - -template -struct is_constructible_tuple> : conjunction...> {}; - -template -struct is_json_iterator_of : std::false_type {}; - -template -struct is_json_iterator_of : std::true_type {}; - -template -struct is_json_iterator_of : std::true_type -{}; - -// checks if a given type T is a template specialization of Primary -template