Skip to content

Commit 504c712

Browse files
committed
allow tokens to be truncated to max input dimension, to match sentence-transformers
1 parent 54c2f2d commit 504c712

7 files changed

Lines changed: 165 additions & 107 deletions

File tree

go.mod

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,10 @@ require (
1313
github.com/mattn/go-isatty v0.0.20
1414
github.com/stretchr/testify v1.10.0
1515
github.com/sugarme/tokenizer v0.2.3-0.20250602044353-04fa4887ad64
16-
github.com/urfave/cli/v2 v2.27.6
16+
github.com/urfave/cli/v2 v2.27.7
1717
github.com/viant/afs v1.26.2
1818
github.com/yalue/onnxruntime_go v1.20.0
19-
golang.org/x/exp v0.0.0-20250606033433-dcc06ee1d476
19+
golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b
2020
)
2121

2222
require (

go.sum

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -89,8 +89,8 @@ github.com/sugarme/regexpset v0.0.0-20200920021344-4d4ec8eaf93c h1:pwb4kNSHb4K89
8989
github.com/sugarme/regexpset v0.0.0-20200920021344-4d4ec8eaf93c/go.mod h1:2gwkXLWbDGUQWeL3RtpCmcY4mzCtU13kb9UsAg9xMaw=
9090
github.com/sugarme/tokenizer v0.2.3-0.20250602044353-04fa4887ad64 h1:WK3kvkoectFqxTImHuBaDgQFMu3ySTZhZjRBvGyACGY=
9191
github.com/sugarme/tokenizer v0.2.3-0.20250602044353-04fa4887ad64/go.mod h1:xjlC2EeLcHnXMO+/H2Dh7n2XXPpGfqLWPiN2OhnbQ4A=
92-
github.com/urfave/cli/v2 v2.27.6 h1:VdRdS98FNhKZ8/Az8B7MTyGQmpIr36O1EHybx/LaZ4g=
93-
github.com/urfave/cli/v2 v2.27.6/go.mod h1:3Sevf16NykTbInEnD0yKkjDAeZDS0A6bzhBH5hrMvTQ=
92+
github.com/urfave/cli/v2 v2.27.7 h1:bH59vdhbjLv3LAvIu6gd0usJHgoTTPhCFib8qqOwXYU=
93+
github.com/urfave/cli/v2 v2.27.7/go.mod h1:CyNAG/xg+iAOg0N4MPGZqVmv2rCoP267496AOXUZjA4=
9494
github.com/viant/afs v1.26.2 h1:rOs/iFxFlEndhIRATJVXlNWhVU0cGdRQAGVTVJPdsc0=
9595
github.com/viant/afs v1.26.2/go.mod h1:rScbFd9LJPGTM8HOI8Kjwee0AZ+MZMupAvFpPg+Qdj4=
9696
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
@@ -103,8 +103,8 @@ github.com/yalue/onnxruntime_go v1.20.0 h1:nPcP2UFeueGF/Ifwu3NBQzvNu8oHlJCul0WGP
103103
github.com/yalue/onnxruntime_go v1.20.0/go.mod h1:b4X26A8pekNb1ACJ58wAXgNKeUCGEAQ9dmACut9Sm/4=
104104
golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM=
105105
golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U=
106-
golang.org/x/exp v0.0.0-20250606033433-dcc06ee1d476 h1:bsqhLWFR6G6xiQcb+JoGqdKdRU6WzPWmK8E0jxTjzo4=
107-
golang.org/x/exp v0.0.0-20250606033433-dcc06ee1d476/go.mod h1:3//PLf8L/X+8b4vuAfHzxeRUl04Adcb341+IGKfnqS8=
106+
golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b h1:M2rDM6z3Fhozi9O7NWsxAkg/yqS/lQJ6PmkyIV3YP+o=
107+
golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b/go.mod h1:3//PLf8L/X+8b4vuAfHzxeRUl04Adcb341+IGKfnqS8=
108108
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
109109
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
110110
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=

pipelineBackends/model.go

Lines changed: 138 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,150 @@
11
package pipelineBackends
22

33
import (
4+
"context"
45
"errors"
6+
"fmt"
7+
"io"
8+
"os"
9+
"strings"
10+
11+
jsoniter "github.com/json-iterator/go"
512

613
"github.com/knights-analytics/hugot/options"
14+
"github.com/knights-analytics/hugot/util"
715
)
816

917
type Model struct {
10-
Path string
11-
OnnxFilename string
12-
OnnxBytes []byte
13-
ORTModel *ORTModel
14-
GoMLXModel *GoMLXModel
15-
Tokenizer *Tokenizer
16-
InputsMeta []InputOutputInfo
17-
OutputsMeta []InputOutputInfo
18-
Destroy func() error
19-
Pipelines map[string]Pipeline
18+
Path string
19+
OnnxFilename string
20+
OnnxBytes []byte
21+
ORTModel *ORTModel
22+
GoMLXModel *GoMLXModel
23+
Tokenizer *Tokenizer
24+
InputsMeta []InputOutputInfo
25+
OutputsMeta []InputOutputInfo
26+
Destroy func() error
27+
Pipelines map[string]Pipeline
28+
MaxPositionEmbeddings int
29+
}
30+
31+
func LoadModel(path string, onnxFilename string, options *options.Options) (*Model, error) {
32+
model := &Model{
33+
Path: path,
34+
OnnxFilename: onnxFilename,
35+
Pipelines: make(map[string]Pipeline),
36+
}
37+
38+
err := LoadOnnxModelBytes(model)
39+
if err != nil {
40+
return nil, err
41+
}
42+
43+
err = loadModelConfig(model)
44+
if err != nil {
45+
return nil, err
46+
}
47+
48+
err = CreateModelBackend(model, options)
49+
if err != nil {
50+
return nil, err
51+
}
52+
53+
tkErr := LoadTokenizer(model, options)
54+
if tkErr != nil {
55+
return nil, tkErr
56+
}
57+
58+
model.Destroy = func() error {
59+
destroyErr := model.Tokenizer.Destroy()
60+
switch options.Backend {
61+
case "ORT":
62+
destroyErr = errors.Join(destroyErr, model.ORTModel.Destroy())
63+
case "GO", "XLA":
64+
model.GoMLXModel.Destroy()
65+
}
66+
return destroyErr
67+
}
68+
return model, nil
69+
}
70+
71+
func LoadOnnxModelBytes(model *Model) error {
72+
var modelOnnxFile string
73+
onnxFiles, err := getOnnxFiles(model.Path)
74+
if err != nil {
75+
return err
76+
}
77+
if len(onnxFiles) == 0 {
78+
return fmt.Errorf("no .onnx file detected at %s. There should be exactly .onnx file", model.Path)
79+
}
80+
if len(onnxFiles) > 1 {
81+
if model.OnnxFilename == "" {
82+
return fmt.Errorf("multiple .onnx file detected at %s and no OnnxFilename specified", model.Path)
83+
}
84+
modelNameFound := false
85+
for i := range onnxFiles {
86+
if onnxFiles[i][1] == model.OnnxFilename {
87+
modelNameFound = true
88+
modelOnnxFile = util.PathJoinSafe(onnxFiles[i]...)
89+
}
90+
}
91+
if !modelNameFound {
92+
return fmt.Errorf("file %s not found at %s", model.OnnxFilename, model.Path)
93+
}
94+
} else {
95+
modelOnnxFile = util.PathJoinSafe(onnxFiles[0]...)
96+
}
97+
98+
onnxBytes, err := util.ReadFileBytes(modelOnnxFile)
99+
if err != nil {
100+
return err
101+
}
102+
103+
model.OnnxBytes = onnxBytes
104+
105+
return err
106+
}
107+
108+
func getOnnxFiles(path string) ([][]string, error) {
109+
var onnxFiles [][]string
110+
walker := func(_ context.Context, _ string, parent string, info os.FileInfo, _ io.Reader) (toContinue bool, err error) {
111+
if strings.HasSuffix(info.Name(), ".onnx") {
112+
onnxFiles = append(onnxFiles, []string{util.PathJoinSafe(path, parent), info.Name()})
113+
}
114+
return true, nil
115+
}
116+
err := util.WalkDir()(context.Background(), path, walker)
117+
return onnxFiles, err
118+
}
119+
120+
func loadModelConfig(model *Model) error {
121+
122+
// load config.json if it exists, to determine max_position_embeddings
123+
configPath := util.PathJoinSafe(model.Path, "config.json")
124+
125+
exists, err := util.FileExists(configPath)
126+
if err != nil {
127+
return err
128+
}
129+
if exists {
130+
configBytes, readErr := util.ReadFileBytes(configPath)
131+
if readErr != nil {
132+
return readErr
133+
}
134+
135+
configMap := map[string]any{}
136+
readErr = jsoniter.Unmarshal(configBytes, &configMap)
137+
if readErr != nil {
138+
return readErr
139+
}
140+
141+
if maxPositionEmbeddingsRaw, existsOk := configMap["max_position_embeddings"]; existsOk {
142+
if maxPositionEmbeddings, castOk := maxPositionEmbeddingsRaw.(float64); castOk {
143+
model.MaxPositionEmbeddings = int(maxPositionEmbeddings)
144+
}
145+
}
146+
}
147+
return nil
20148
}
21149

22150
func ReshapeOutput(input *[]float32, meta InputOutputInfo, paddingMask [][]bool, sequenceLength int) OutputArray {
@@ -84,38 +212,3 @@ func flatDataTo3D(input *[]float32, paddingMask [][]bool, sequenceLength int, di
84212

85213
return output
86214
}
87-
88-
func LoadModel(path string, onnxFilename string, options *options.Options) (*Model, error) {
89-
model := &Model{
90-
Path: path,
91-
OnnxFilename: onnxFilename,
92-
Pipelines: make(map[string]Pipeline),
93-
}
94-
95-
err := LoadOnnxModelBytes(model)
96-
if err != nil {
97-
return nil, err
98-
}
99-
100-
err = CreateModelBackend(model, options)
101-
if err != nil {
102-
return nil, err
103-
}
104-
105-
tkErr := LoadTokenizer(model, options)
106-
if tkErr != nil {
107-
return nil, tkErr
108-
}
109-
110-
model.Destroy = func() error {
111-
destroyErr := model.Tokenizer.Destroy()
112-
switch options.Backend {
113-
case "ORT":
114-
destroyErr = errors.Join(destroyErr, model.ORTModel.Destroy())
115-
case "GO", "XLA":
116-
model.GoMLXModel.Destroy()
117-
}
118-
return destroyErr
119-
}
120-
return model, nil
121-
}

pipelineBackends/pipeline.go

Lines changed: 0 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,9 @@
11
package pipelineBackends
22

33
import (
4-
"context"
54
"fmt"
6-
"io"
7-
"os"
8-
"strings"
95

106
"github.com/knights-analytics/hugot/options"
11-
"github.com/knights-analytics/hugot/util"
127
)
138

149
// BasePipeline can be embedded by a pipeline.
@@ -173,55 +168,6 @@ func NewBasePipeline[T Pipeline](config PipelineConfig[T], s *options.Options, m
173168
return pipeline, nil
174169
}
175170

176-
func LoadOnnxModelBytes(model *Model) error {
177-
var modelOnnxFile string
178-
onnxFiles, err := getOnnxFiles(model.Path)
179-
if err != nil {
180-
return err
181-
}
182-
if len(onnxFiles) == 0 {
183-
return fmt.Errorf("no .onnx file detected at %s. There should be exactly .onnx file", model.Path)
184-
}
185-
if len(onnxFiles) > 1 {
186-
if model.OnnxFilename == "" {
187-
return fmt.Errorf("multiple .onnx file detected at %s and no OnnxFilename specified", model.Path)
188-
}
189-
modelNameFound := false
190-
for i := range onnxFiles {
191-
if onnxFiles[i][1] == model.OnnxFilename {
192-
modelNameFound = true
193-
modelOnnxFile = util.PathJoinSafe(onnxFiles[i]...)
194-
}
195-
}
196-
if !modelNameFound {
197-
return fmt.Errorf("file %s not found at %s", model.OnnxFilename, model.Path)
198-
}
199-
} else {
200-
modelOnnxFile = util.PathJoinSafe(onnxFiles[0]...)
201-
}
202-
203-
onnxBytes, err := util.ReadFileBytes(modelOnnxFile)
204-
if err != nil {
205-
return err
206-
}
207-
208-
model.OnnxBytes = onnxBytes
209-
210-
return err
211-
}
212-
213-
func getOnnxFiles(path string) ([][]string, error) {
214-
var onnxFiles [][]string
215-
walker := func(_ context.Context, _ string, parent string, info os.FileInfo, _ io.Reader) (toContinue bool, err error) {
216-
if strings.HasSuffix(info.Name(), ".onnx") {
217-
onnxFiles = append(onnxFiles, []string{util.PathJoinSafe(path, parent), info.Name()})
218-
}
219-
return true, nil
220-
}
221-
err := util.WalkDir()(context.Background(), path, walker)
222-
return onnxFiles, err
223-
}
224-
225171
func CreateModelBackend(model *Model, s *options.Options) error {
226172
// creation of the session. Only one output (either token or sentence embedding).
227173
var err error

pipelineBackends/tokenizer.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ type Tokenizer struct {
1212
RustTokenizer *RustTokenizer
1313
GoTokenizer *GoTokenizer
1414
TokenizerTimings *timings
15+
MaxAllowedTokens int
1516
Destroy func() error
1617
}
1718

pipelineBackends/tokenizer_go.go

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ func loadGoTokenizer(tokenizerBytes []byte, model *Model) error {
2121
if tkErr != nil {
2222
return tkErr
2323
}
24-
model.Tokenizer = &Tokenizer{Runtime: "GO", GoTokenizer: &GoTokenizer{Tokenizer: tk}, TokenizerTimings: &timings{}, Destroy: func() error {
24+
model.Tokenizer = &Tokenizer{Runtime: "GO", GoTokenizer: &GoTokenizer{Tokenizer: tk}, TokenizerTimings: &timings{}, MaxAllowedTokens: model.MaxPositionEmbeddings, Destroy: func() error {
2525
return nil
2626
}}
2727
return nil
@@ -38,6 +38,15 @@ func tokenizeInputsGo(batch *PipelineBatch, tk *Tokenizer, inputs []string) {
3838
log.Fatal(err)
3939
}
4040

41+
if tk.MaxAllowedTokens > 0 && len(output.Tokens) > tk.MaxAllowedTokens {
42+
output.Tokens = output.Tokens[:tk.MaxAllowedTokens]
43+
output.Ids = output.Ids[:min(len(output.Ids), tk.MaxAllowedTokens)]
44+
output.TypeIds = output.TypeIds[:min(len(output.TypeIds), tk.MaxAllowedTokens)]
45+
output.AttentionMask = output.AttentionMask[:min(len(output.AttentionMask), tk.MaxAllowedTokens)]
46+
output.SpecialTokenMask = output.SpecialTokenMask[:min(len(output.SpecialTokenMask), tk.MaxAllowedTokens)]
47+
output.Offsets = output.Offsets[:min(len(output.Offsets), tk.MaxAllowedTokens)]
48+
}
49+
4150
maxAttentionIndex := 0
4251
for j, attentionMaskValue := range output.AttentionMask {
4352
if attentionMaskValue != 0 {

pipelineBackends/tokenizer_rust.go

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ func loadRustTokenizer(tokenizerBytes []byte, model *Model) error {
2424
if optErr != nil {
2525
return optErr
2626
}
27-
model.Tokenizer = &Tokenizer{Runtime: "RUST", RustTokenizer: &RustTokenizer{Tokenizer: tk, Options: rustOptions}, TokenizerTimings: &timings{}, Destroy: func() error {
27+
model.Tokenizer = &Tokenizer{Runtime: "RUST", RustTokenizer: &RustTokenizer{Tokenizer: tk, Options: rustOptions}, TokenizerTimings: &timings{}, MaxAllowedTokens: model.MaxPositionEmbeddings, Destroy: func() error {
2828
return tk.Close()
2929
}}
3030
return nil
@@ -57,6 +57,15 @@ func tokenizeInputsRust(batch *PipelineBatch, tk *Tokenizer, inputs []string) {
5757
rustTK.Options...,
5858
)
5959

60+
if tk.MaxAllowedTokens > 0 && len(output.Tokens) > tk.MaxAllowedTokens {
61+
output.Tokens = output.Tokens[:tk.MaxAllowedTokens]
62+
output.IDs = output.IDs[:min(len(output.IDs), tk.MaxAllowedTokens)]
63+
output.TypeIDs = output.TypeIDs[:min(len(output.TypeIDs), tk.MaxAllowedTokens)]
64+
output.AttentionMask = output.AttentionMask[:min(len(output.AttentionMask), tk.MaxAllowedTokens)]
65+
output.SpecialTokensMask = output.SpecialTokensMask[:min(len(output.SpecialTokensMask), tk.MaxAllowedTokens)]
66+
output.Offsets = output.Offsets[:min(len(output.Offsets), tk.MaxAllowedTokens)]
67+
}
68+
6069
maxAttentionIndex := 0
6170
for j, attentionMaskValue := range output.AttentionMask {
6271
if attentionMaskValue != 0 {

0 commit comments

Comments
 (0)