Skip to content
GitLab
Menu
Projects
Groups
Snippets
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
Menu
Open sidebar
OpenDAS
ollama
Commits
c088ac0e
"git@developer.sourcefind.cn:OpenDAS/ollama.git" did not exist on "1b308e1d2a478e70ef3e31e6b24d687a44b33016"
Unverified
Commit
c088ac0e
authored
Jun 20, 2025
by
Michael Yang
Committed by
GitHub
Jun 20, 2025
Browse files
convert: utility for merging tensors (#11069)
parent
0a066cfd
Changes
3
Hide whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
174 additions
and
53 deletions
+174
-53
convert/convert_mixtral.go
convert/convert_mixtral.go
+23
-53
convert/tensor.go
convert/tensor.go
+53
-0
convert/tensor_test.go
convert/tensor_test.go
+98
-0
No files found.
convert/convert_mixtral.go
View file @
c088ac0e
...
...
@@ -2,9 +2,6 @@ package convert
import
(
"fmt"
"io"
"slices"
"strings"
"github.com/ollama/ollama/fs/ggml"
)
...
...
@@ -30,65 +27,38 @@ func (p *mixtralModel) KV(t *Tokenizer) ggml.KV {
}
func
(
p
*
mixtralModel
)
Tensors
(
ts
[]
Tensor
)
[]
*
ggml
.
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
[]
*
ggml
.
Tensor
for
n
,
e
:=
range
experts
{
// TODO(mxyng): sanity check experts
out
=
append
(
out
,
&
ggml
.
Tensor
{
Name
:
n
,
Kind
:
e
[
0
]
.
Kind
(),
Shape
:
append
([]
uint64
{
uint64
(
len
(
e
))},
e
[
0
]
.
Shape
()
...
),
WriterTo
:
e
,
merges
:=
make
([]
merge
,
0
,
p
.
NumHiddenLayers
*
6
)
for
i
:=
range
p
.
NumHiddenLayers
{
merges
=
append
(
merges
,
merge
{
fmt
.
Sprintf
(
"blk.%d.*.w1.weight"
,
i
),
fmt
.
Sprintf
(
"blk.%d.ffn_gate_exps.weight"
,
i
),
},
merge
{
fmt
.
Sprintf
(
"blk.%d.*.w1.bias"
,
i
),
fmt
.
Sprintf
(
"blk.%d.ffn_gate_exps.bias"
,
i
),
},
merge
{
fmt
.
Sprintf
(
"blk.%d.*.w2.weight"
,
i
),
fmt
.
Sprintf
(
"blk.%d.ffn_up_exps.weight"
,
i
),
},
merge
{
fmt
.
Sprintf
(
"blk.%d.*.w2.bias"
,
i
),
fmt
.
Sprintf
(
"blk.%d.ffn_up_exps.bias"
,
i
),
},
merge
{
fmt
.
Sprintf
(
"blk.%d.*.w3.weight"
,
i
),
fmt
.
Sprintf
(
"blk.%d.ffn_down_exps.weight"
,
i
),
},
merge
{
fmt
.
Sprintf
(
"blk.%d.*.w3.bias"
,
i
),
fmt
.
Sprintf
(
"blk.%d.ffn_down_exps.bias"
,
i
),
})
}
out
,
ts
:=
mergeTensors
(
ts
,
merges
...
)
return
append
(
out
,
p
.
llamaModel
.
Tensors
(
ts
)
...
)
}
func
(
p
*
mixtralModel
)
Replacements
()
[]
string
{
return
append
(
p
.
llamaModel
.
Replacements
(),
"model.layers"
,
"blk"
,
"block_sparse_moe.gate"
,
"ffn_gate_inp"
,
"block_sparse_moe.experts."
,
"."
,
)
}
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
}
convert/tensor.go
View file @
c088ac0e
...
...
@@ -2,7 +2,9 @@ package convert
import
(
"cmp"
"io"
"iter"
"path"
"slices"
"strings"
...
...
@@ -74,3 +76,54 @@ func splitDim(t Tensor, dim int, splits ...split) iter.Seq[*ggml.Tensor] {
}
}
}
type
merge
struct
{
pattern
,
name
string
}
// mergeTensors merges tensors that match a given pattern into a single tensor.
func
mergeTensors
(
unmatched
[]
Tensor
,
merges
...
merge
)
(
out
[]
*
ggml
.
Tensor
,
_
[]
Tensor
)
{
var
matched
[]
Tensor
for
i
:=
range
merges
{
matched
,
unmatched
=
slicesSplitFunc
(
unmatched
,
func
(
t
Tensor
)
bool
{
matched
,
_
:=
path
.
Match
(
merges
[
i
]
.
pattern
,
t
.
Name
())
return
matched
})
if
len
(
matched
)
>
0
{
out
=
append
(
out
,
&
ggml
.
Tensor
{
Name
:
merges
[
i
]
.
name
,
Kind
:
matched
[
0
]
.
Kind
(),
Shape
:
append
([]
uint64
{
uint64
(
len
(
matched
))},
matched
[
0
]
.
Shape
()
...
),
WriterTo
:
mergeGroup
(
matched
),
})
}
}
return
out
,
unmatched
}
// slicesSplitFunc splits a slice into two slices based on a predicate function.
func
slicesSplitFunc
[
S
~
[]
E
,
E
comparable
](
s
S
,
fn
func
(
e
E
)
bool
)
(
matched
,
unmatched
S
)
{
for
_
,
e
:=
range
s
{
if
fn
(
e
)
{
matched
=
append
(
matched
,
e
)
}
else
{
unmatched
=
append
(
unmatched
,
e
)
}
}
return
matched
,
unmatched
}
type
mergeGroup
[]
Tensor
func
(
g
mergeGroup
)
WriteTo
(
w
io
.
Writer
)
(
int64
,
error
)
{
for
_
,
t
:=
range
g
{
if
_
,
err
:=
t
.
WriteTo
(
w
);
err
!=
nil
{
return
0
,
err
}
}
return
0
,
nil
}
convert/tensor_test.go
View file @
c088ac0e
...
...
@@ -9,6 +9,8 @@ import (
"strings"
"testing"
"github.com/google/go-cmp/cmp"
"github.com/ollama/ollama/fs/ggml"
"github.com/pdevine/tensor"
)
...
...
@@ -302,3 +304,99 @@ func TestSplitDim(t *testing.T) {
}
})
}
func
TestMerge
(
t
*
testing
.
T
)
{
unmatched
:=
[]
Tensor
{
&
fakeTensor
{
name
:
"a.0.b"
,
shape
:
[]
uint64
{
5
,
2
},
data
:
[]
float32
{
10
,
11
,
12
,
13
,
14
,
15
,
16
,
17
,
18
,
19
},
},
&
fakeTensor
{
name
:
"a.1.b"
,
shape
:
[]
uint64
{
5
,
2
},
data
:
[]
float32
{
20
,
21
,
22
,
23
,
24
,
25
,
26
,
27
,
28
,
29
},
},
&
fakeTensor
{
name
:
"c.0.d"
,
shape
:
[]
uint64
{
5
,
2
},
data
:
[]
float32
{
30
,
31
,
32
,
33
,
34
,
35
,
36
,
37
,
38
,
39
},
},
&
fakeTensor
{
name
:
"c.1.d"
,
shape
:
[]
uint64
{
5
,
2
},
data
:
[]
float32
{
40
,
41
,
42
,
43
,
44
,
45
,
46
,
47
,
48
,
49
},
},
&
fakeTensor
{
name
:
"e.0.f"
,
shape
:
[]
uint64
{
5
,
2
},
data
:
[]
float32
{
50
,
51
,
52
,
53
,
54
,
55
,
56
,
57
,
58
,
59
},
},
}
checkMatched
:=
func
(
t
*
testing
.
T
,
n
int
,
matched
[]
*
ggml
.
Tensor
)
{
for
i
:=
range
n
{
got
:=
matched
[
i
]
if
diff
:=
cmp
.
Diff
([]
uint64
{
2
,
5
,
2
},
got
.
Shape
);
diff
!=
""
{
t
.
Errorf
(
"unexpected (-want +got):
\n
%s"
,
diff
)
}
var
b
bytes
.
Buffer
if
_
,
err
:=
got
.
WriteTo
(
&
b
);
err
!=
nil
{
t
.
Fatal
(
err
)
}
f32s
:=
make
([]
float32
,
20
)
if
err
:=
binary
.
Read
(
&
b
,
binary
.
LittleEndian
,
&
f32s
);
err
!=
nil
{
t
.
Fatal
(
err
)
}
offset
:=
10
+
(
i
*
20
)
want
:=
make
([]
float32
,
20
)
for
j
:=
range
20
{
want
[
j
]
=
float32
(
offset
+
j
)
}
if
diff
:=
cmp
.
Diff
(
want
,
f32s
);
diff
!=
""
{
t
.
Errorf
(
"unexpected data (-want +got):
\n
%s"
,
diff
)
}
}
}
t
.
Run
(
"single merge"
,
func
(
t
*
testing
.
T
)
{
matched
,
unmatched
:=
mergeTensors
(
unmatched
,
merge
{
"a.*.b"
,
"a.b"
})
if
len
(
unmatched
)
!=
3
{
t
.
Error
(
"expected 3 remaining tensors, got"
,
len
(
unmatched
))
}
if
len
(
matched
)
!=
1
{
t
.
Error
(
"expected 1 merged tensor, got"
,
len
(
matched
))
}
checkMatched
(
t
,
1
,
matched
)
})
t
.
Run
(
"multiple merges"
,
func
(
t
*
testing
.
T
)
{
matched
,
unmatched
:=
mergeTensors
(
unmatched
,
merge
{
"a.*.b"
,
"a.b"
},
merge
{
"c.*.d"
,
"c.d"
})
if
len
(
unmatched
)
!=
1
{
t
.
Error
(
"expected 1 remaining tensors, got"
,
len
(
unmatched
))
}
if
len
(
matched
)
!=
2
{
t
.
Error
(
"expected 2 merged tensor, got"
,
len
(
matched
))
}
checkMatched
(
t
,
2
,
matched
)
})
t
.
Run
(
"no match"
,
func
(
t
*
testing
.
T
)
{
matched
,
unmatched
:=
mergeTensors
(
unmatched
,
merge
{
"x.*.y"
,
"x.y"
})
if
len
(
unmatched
)
!=
5
{
t
.
Error
(
"expected 5 remaining tensors, got"
,
len
(
unmatched
))
}
if
len
(
matched
)
!=
0
{
t
.
Error
(
"expected no merged tensors, got"
,
len
(
matched
))
}
})
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment