2025-02-14 08:31:21 +08:00
|
|
|
package model
|
|
|
|
|
|
|
|
import (
|
2025-09-19 07:11:08 +08:00
|
|
|
"errors"
|
2025-02-14 08:31:21 +08:00
|
|
|
"reflect"
|
|
|
|
"slices"
|
|
|
|
"testing"
|
|
|
|
|
|
|
|
"github.com/google/go-cmp/cmp"
|
2025-03-19 05:38:44 +08:00
|
|
|
"github.com/ollama/ollama/fs"
|
|
|
|
fsggml "github.com/ollama/ollama/fs/ggml"
|
2025-02-14 08:31:21 +08:00
|
|
|
"github.com/ollama/ollama/ml"
|
|
|
|
"github.com/ollama/ollama/ml/backend/ggml"
|
|
|
|
"github.com/ollama/ollama/ml/nn"
|
|
|
|
)
|
|
|
|
|
|
|
|
func TestParseTags(t *testing.T) {
|
|
|
|
cases := []struct {
|
|
|
|
value string
|
|
|
|
want Tag
|
|
|
|
}{
|
|
|
|
{
|
|
|
|
value: "output",
|
|
|
|
want: Tag{
|
2025-09-24 07:08:57 +08:00
|
|
|
name: "output",
|
2025-02-14 08:31:21 +08:00
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
value: "output,alt:token_embd",
|
|
|
|
want: Tag{
|
2025-09-24 07:08:57 +08:00
|
|
|
name: "output",
|
|
|
|
alternatives: []string{
|
2025-02-14 08:31:21 +08:00
|
|
|
"token_embd",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, tt := range cases {
|
|
|
|
t.Run(tt.value, func(t *testing.T) {
|
2025-09-24 07:08:57 +08:00
|
|
|
got := parseTag(tt.value)
|
|
|
|
if diff := cmp.Diff(tt.want, got, cmp.AllowUnexported((Tag{}))); diff != "" {
|
2025-02-14 08:31:21 +08:00
|
|
|
t.Errorf("ParseTags() returned unexpected values (-want +got):\n%s", diff)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
type fakeBackend struct {
|
|
|
|
*ggml.Backend
|
|
|
|
names []string
|
|
|
|
}
|
|
|
|
|
|
|
|
type fakeTensor struct {
|
|
|
|
*ggml.Tensor
|
|
|
|
Name string
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *fakeBackend) Get(name string) ml.Tensor {
|
|
|
|
if slices.Contains(m.names, name) {
|
|
|
|
return &fakeTensor{Name: name}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestPopulateFields(t *testing.T) {
|
|
|
|
type fakeLayer struct {
|
|
|
|
Query *nn.Linear `gguf:"attn_q"`
|
|
|
|
Key *nn.Linear `gguf:"attn_k"`
|
|
|
|
Value *nn.Linear `gguf:"attn_v"`
|
|
|
|
Output *nn.Linear `gguf:"attn_o"`
|
|
|
|
}
|
|
|
|
|
|
|
|
type fakeModel struct {
|
|
|
|
Input *nn.Embedding `gguf:"input"`
|
|
|
|
OutputNorm *nn.RMSNorm `gguf:"output_norm"`
|
|
|
|
Output *nn.Linear `gguf:"output"`
|
|
|
|
Layers [2]fakeLayer `gguf:"blk"`
|
|
|
|
}
|
|
|
|
|
|
|
|
var m fakeModel
|
|
|
|
v := reflect.ValueOf(&m)
|
2024-12-18 11:59:41 +08:00
|
|
|
v.Elem().Set(populateFields(Base{b: &fakeBackend{
|
2025-02-14 08:31:21 +08:00
|
|
|
names: []string{
|
|
|
|
"input.weight",
|
|
|
|
"blk.0.attn_q.weight",
|
|
|
|
"blk.0.attn_k.weight",
|
|
|
|
"blk.0.attn_v.weight",
|
|
|
|
"blk.1.attn_q.weight",
|
|
|
|
"blk.1.attn_k.weight",
|
|
|
|
"blk.1.attn_v.weight",
|
|
|
|
"output_norm.weight",
|
|
|
|
"output.weight",
|
|
|
|
},
|
2024-12-18 11:59:41 +08:00
|
|
|
}}, v.Elem()))
|
2025-02-14 08:31:21 +08:00
|
|
|
|
|
|
|
if diff := cmp.Diff(fakeModel{
|
|
|
|
Input: &nn.Embedding{Weight: &fakeTensor{Name: "input.weight"}},
|
|
|
|
OutputNorm: &nn.RMSNorm{Weight: &fakeTensor{Name: "output_norm.weight"}},
|
|
|
|
Output: &nn.Linear{Weight: &fakeTensor{Name: "output.weight"}},
|
|
|
|
Layers: [2]fakeLayer{
|
|
|
|
{
|
|
|
|
Query: &nn.Linear{Weight: &fakeTensor{Name: "blk.0.attn_q.weight"}},
|
|
|
|
Key: &nn.Linear{Weight: &fakeTensor{Name: "blk.0.attn_k.weight"}},
|
|
|
|
Value: &nn.Linear{Weight: &fakeTensor{Name: "blk.0.attn_v.weight"}},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Query: &nn.Linear{Weight: &fakeTensor{Name: "blk.1.attn_q.weight"}},
|
|
|
|
Key: &nn.Linear{Weight: &fakeTensor{Name: "blk.1.attn_k.weight"}},
|
|
|
|
Value: &nn.Linear{Weight: &fakeTensor{Name: "blk.1.attn_v.weight"}},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}, m); diff != "" {
|
|
|
|
t.Errorf("populateFields() set incorrect values (-want +got):\n%s", diff)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestPopulateFieldsAlternateName(t *testing.T) {
|
2025-08-15 05:42:58 +08:00
|
|
|
type nested struct {
|
|
|
|
Weight *nn.Linear `gguf:"a,alt:b"`
|
|
|
|
}
|
|
|
|
|
2025-02-14 08:31:21 +08:00
|
|
|
type fakeModel struct {
|
|
|
|
Input *nn.Embedding `gguf:"input"`
|
|
|
|
Output *nn.Linear `gguf:"output,alt:input"`
|
2025-08-15 05:42:58 +08:00
|
|
|
Nested *nested `gguf:"nested"`
|
2025-09-24 08:50:53 +08:00
|
|
|
Tensor ml.Tensor `gguf:"leaf,alt:tensor"`
|
2025-02-14 08:31:21 +08:00
|
|
|
}
|
|
|
|
|
2025-08-15 05:42:58 +08:00
|
|
|
var m fakeModel
|
2025-02-14 08:31:21 +08:00
|
|
|
v := reflect.ValueOf(&m)
|
2024-12-18 11:59:41 +08:00
|
|
|
v.Elem().Set(populateFields(Base{b: &fakeBackend{
|
2025-02-14 08:31:21 +08:00
|
|
|
names: []string{
|
|
|
|
"input.weight",
|
2025-08-15 05:42:58 +08:00
|
|
|
"nested.b.weight",
|
2025-09-24 08:50:53 +08:00
|
|
|
"leaf",
|
2025-02-14 08:31:21 +08:00
|
|
|
},
|
2024-12-18 11:59:41 +08:00
|
|
|
}}, v.Elem()))
|
2025-02-14 08:31:21 +08:00
|
|
|
|
|
|
|
if diff := cmp.Diff(fakeModel{
|
|
|
|
Input: &nn.Embedding{Weight: &fakeTensor{Name: "input.weight"}},
|
|
|
|
Output: &nn.Linear{Weight: &fakeTensor{Name: "input.weight"}},
|
2025-08-15 05:42:58 +08:00
|
|
|
Nested: &nested{
|
|
|
|
Weight: &nn.Linear{Weight: &fakeTensor{Name: "nested.b.weight"}},
|
|
|
|
},
|
2025-09-24 08:50:53 +08:00
|
|
|
Tensor: &fakeTensor{Name: "leaf"},
|
2025-02-14 08:31:21 +08:00
|
|
|
}, m); diff != "" {
|
|
|
|
t.Errorf("populateFields() set incorrect values (-want +got):\n%s", diff)
|
|
|
|
}
|
|
|
|
}
|
2025-03-05 01:03:46 +08:00
|
|
|
|
2025-09-24 07:08:57 +08:00
|
|
|
func TestPopulateFieldsPrefixSuffixName(t *testing.T) {
|
|
|
|
type fakeBlock struct {
|
|
|
|
A *nn.Linear `gguf:"a"`
|
|
|
|
B *nn.Linear `gguf:",pre:b_"`
|
|
|
|
C *nn.Linear `gguf:",suf:_c"`
|
|
|
|
XY *nn.Linear `gguf:",pre:x_,suf:_y"`
|
|
|
|
}
|
|
|
|
|
|
|
|
type fakeModel struct {
|
|
|
|
Blocks []fakeBlock `gguf:"blk"`
|
|
|
|
}
|
|
|
|
|
|
|
|
m := fakeModel{
|
|
|
|
Blocks: make([]fakeBlock, 2),
|
|
|
|
}
|
|
|
|
v := reflect.ValueOf(&m)
|
|
|
|
v.Elem().Set(populateFields(Base{b: &fakeBackend{
|
|
|
|
names: []string{
|
|
|
|
"blk.0.a.weight",
|
|
|
|
"blk.0.b_weight",
|
|
|
|
"blk.0.b_bias",
|
|
|
|
"blk.0.weight_c",
|
|
|
|
"blk.0.x_weight_y",
|
|
|
|
"blk.1.a.weight",
|
|
|
|
"blk.1.b_weight",
|
|
|
|
"blk.1.b_bias",
|
|
|
|
"blk.1.weight_c",
|
|
|
|
"blk.1.x_weight_y",
|
|
|
|
},
|
|
|
|
}}, v.Elem()))
|
|
|
|
|
|
|
|
if diff := cmp.Diff(fakeModel{
|
|
|
|
Blocks: []fakeBlock{
|
|
|
|
{
|
|
|
|
A: &nn.Linear{Weight: &fakeTensor{Name: "blk.0.a.weight"}},
|
|
|
|
B: &nn.Linear{Weight: &fakeTensor{Name: "blk.0.b_weight"}, Bias: &fakeTensor{Name: "blk.0.b_bias"}},
|
|
|
|
C: &nn.Linear{Weight: &fakeTensor{Name: "blk.0.weight_c"}},
|
|
|
|
XY: &nn.Linear{Weight: &fakeTensor{Name: "blk.0.x_weight_y"}},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
A: &nn.Linear{Weight: &fakeTensor{Name: "blk.1.a.weight"}},
|
|
|
|
B: &nn.Linear{Weight: &fakeTensor{Name: "blk.1.b_weight"}, Bias: &fakeTensor{Name: "blk.1.b_bias"}},
|
|
|
|
C: &nn.Linear{Weight: &fakeTensor{Name: "blk.1.weight_c"}},
|
|
|
|
XY: &nn.Linear{Weight: &fakeTensor{Name: "blk.1.x_weight_y"}},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}, m); diff != "" {
|
|
|
|
t.Errorf("populateFields() set incorrect values (-want +got):\n%s", diff)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-09-19 07:11:08 +08:00
|
|
|
func TestModelForArch(t *testing.T) {
|
|
|
|
type fakeModel struct {
|
|
|
|
Model
|
2025-03-05 01:03:46 +08:00
|
|
|
}
|
|
|
|
|
2025-09-19 07:11:08 +08:00
|
|
|
type fakeEmbeddingModel struct {
|
|
|
|
Model
|
2025-03-05 01:03:46 +08:00
|
|
|
}
|
|
|
|
|
2025-09-19 07:11:08 +08:00
|
|
|
models["model"] = func(c fs.Config) (Model, error) { return fakeModel{}, nil }
|
|
|
|
models["model_embed"] = func(c fs.Config) (Model, error) { return fakeEmbeddingModel{}, nil }
|
2025-03-05 01:03:46 +08:00
|
|
|
|
2025-09-19 07:11:08 +08:00
|
|
|
cases := []struct {
|
|
|
|
name string
|
|
|
|
config fs.Config
|
|
|
|
want any
|
|
|
|
err error
|
|
|
|
}{
|
|
|
|
{
|
|
|
|
name: "model",
|
|
|
|
config: fsggml.KV{
|
|
|
|
"general.architecture": "model",
|
|
|
|
},
|
|
|
|
want: fakeModel{},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "embedding",
|
|
|
|
config: fsggml.KV{
|
|
|
|
"general.architecture": "model",
|
|
|
|
"model.pooling_type": uint32(1),
|
|
|
|
},
|
|
|
|
want: fakeEmbeddingModel{},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "unsupported",
|
|
|
|
config: fsggml.KV{
|
|
|
|
"general.architecture": "unsupported",
|
|
|
|
},
|
|
|
|
err: ErrUnsupportedModel,
|
|
|
|
},
|
|
|
|
}
|
2025-03-05 01:03:46 +08:00
|
|
|
|
2025-09-19 07:11:08 +08:00
|
|
|
for _, tt := range cases {
|
|
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
|
|
got, err := modelForArch(tt.config)
|
|
|
|
if !errors.Is(err, tt.err) {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
2025-03-05 01:03:46 +08:00
|
|
|
|
2025-09-19 07:11:08 +08:00
|
|
|
if diff := cmp.Diff(tt.want, got); diff != "" {
|
|
|
|
t.Errorf("modelForArch() returned unexpected values (-want +got):\n%s", diff)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
2025-03-05 01:03:46 +08:00
|
|
|
}
|