Skip to content

Commit 30cabfc

Browse files
Merge pull request #12 from knights-analytics/huggingfaceDownloader
feat: adding ability to download models
2 parents b29c986 + 3db0c10 commit 30cabfc

15 files changed

Lines changed: 543 additions & 99 deletions

.golangci.yml

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
linters:
2+
disable-all: true
3+
enable:
4+
# Check for pass []any as any in variadic func(...any).
5+
# Rare case but saved me from debugging a few times.
6+
- asasalint
7+
8+
# I prefer plane ASCII identifiers.
9+
# Symbol `∆` instead of `delta` looks cool but no thanks.
10+
- asciicheck
11+
12+
# Checks for dangerous unicode character sequences.
13+
# Super rare but why not to be a bit paranoid?
14+
- bidichk
15+
16+
# Checks whether HTTP response body is closed successfully.
17+
- bodyclose
18+
19+
# Check whether the function uses a non-inherited context.
20+
- contextcheck
21+
22+
# Check for two durations multiplied together.
23+
- durationcheck
24+
25+
# Forces to not skip error check.
26+
- errcheck
27+
28+
# Checks `Err-` prefix for var and `-Error` suffix for error type.
29+
- errname
30+
31+
# Suggests to use `%w` for error-wrapping.
32+
- errorlint
33+
34+
# Checks for pointers to enclosing loop variables.
35+
- exportloopref
36+
37+
# Forces to put `.` at the end of the comment. Code is poetry.
38+
- godot
39+
40+
# Might not be that important but I prefer to keep all of them.
41+
# `gofumpt` is amazing, kudos to Daniel Marti https://github.com/mvdan/gofumpt
42+
- gofmt
43+
- gofumpt
44+
- goimports
45+
46+
# Allow or ban replace directives in go.mod
47+
# or force explanation for retract directives.
48+
- gomoddirectives
49+
50+
# Powerful security-oriented linter. But requires some time to
51+
# configure it properly, see https://github.com/securego/gosec#available-rules
52+
- gosec
53+
54+
# Linter that specializes in simplifying code.
55+
- gosimple
56+
57+
# Official Go tool. Must have.
58+
- govet
59+
60+
# Detects when assignments to existing variables are not used
61+
# Last week I caught a bug with it.
62+
- ineffassign
63+
64+
# Fix all the misspells, amazing thing.
65+
- misspell
66+
67+
# Finds naked/bare returns and requires change them.
68+
- nakedret
69+
70+
# Both require a bit more explicit returns.
71+
- nilerr
72+
- nilnil
73+
74+
# Finds sending HTTP request without context.Context.
75+
- noctx
76+
77+
# Forces comment why another check is disabled.
78+
# Better not to have //nolint: at all ;)
79+
- nolintlint
80+
81+
# Finds slices that could potentially be pre-allocated.
82+
# Small performance win + cleaner code.
83+
- prealloc
84+
85+
# Finds shadowing of Go's predeclared identifiers.
86+
# I hear a lot of complaints from junior developers.
87+
# But after some time they find it very useful.
88+
- predeclared
89+
90+
# Lint your Prometheus metrics name.
91+
- promlinter
92+
93+
# Checks that package variables are not reassigned.
94+
# Super rare case but can catch bad things (like `io.EOF = nil`)
95+
- reassign
96+
97+
# Drop-in replacement of `golint`.
98+
- revive
99+
100+
# Somewhat similar to `bodyclose` but for `database/sql` package.
101+
- rowserrcheck
102+
- sqlclosecheck
103+
104+
# I have found that it's not the same as staticcheck binary :\
105+
- staticcheck
106+
107+
# Is a replacement for `golint`, similar to `revive`.
108+
- stylecheck
109+
110+
# Check struct tags.
111+
- tagliatelle
112+
113+
# Test-related checks. All of them are good.
114+
- tenv
115+
- testableexamples
116+
- thelper
117+
- tparallel
118+
119+
# Remove unnecessary type conversions, make code cleaner
120+
- unconvert
121+
122+
# Might be noisy but better to know what is unused
123+
- unparam
124+
125+
# Must have. Finds unused declarations.
126+
- unused
127+
128+
# Detect the possibility to use variables/constants from stdlib.
129+
- usestdlibvars
130+
131+
# Finds wasted assignment statements.
132+
- wastedassign

Dockerfile

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
ARG GO_VERSION=1.22.0
1+
ARG GO_VERSION=1.22.1
22
ARG RUST_VERSION=1.76
33
ARG ONNXRUNTIME_VERSION=1.17.1
44

@@ -37,8 +37,6 @@ RUN GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -o test2json -ldflags="-s -w"
3737
curl -LO https://github.com/gotestyourself/gotestsum/releases/download/v1.11.0/gotestsum_1.11.0_linux_amd64.tar.gz && \
3838
tar -xzf gotestsum_1.11.0_linux_amd64.tar.gz --directory /usr/local/bin
3939

40-
COPY ./models /models
41-
4240
# build cli
4341
COPY . /build
4442
WORKDIR /build
@@ -47,7 +45,7 @@ RUN cd ./cmd && CGO_ENABLED=1 GOOS=linux GOARCH=amd64 go build -a -o ./target ma
4745
# NON-PRIVILEDGED USER
4846
# create non-priviledged testuser with id: 1000
4947
RUN dnf install --disablerepo=* --enablerepo=amazonlinux --allowerasing -y dirmngr && dnf clean all
50-
RUN useradd -u 1000 -m testuser
48+
RUN useradd -u 1000 -m testuser && chown -R testuser:testuser /build
5149

5250
# ENTRYPOINT
5351
COPY ./scripts/entrypoint.sh /entrypoint.sh
@@ -62,4 +60,4 @@ FROM scratch AS artifacts
6260

6361
COPY --from=building /usr/lib64/onnxruntime.so onnxruntime.so
6462
COPY --from=building /usr/lib/libtokenizers.a libtokenizers.a
65-
COPY --from=building /build/cmd/target /hugot-cli-linux-amd64
63+
COPY --from=building /build/cmd/target /hugot-cli-linux-amd64

README.md

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -100,9 +100,12 @@ defer func(session *hugot.Session) {
100100
err := session.Destroy()
101101
check(err)
102102
}(session)
103-
// we now create a text classification pipeline. It requires the path to the onnx model folder,
103+
// Let's download an onnx sentiment test classification model in the current directory
104+
modelPath, err := session.DownloadModel("KnightsAnalytics/distilbert-base-uncased-finetuned-sst-2-english", "./")
105+
check(err)
106+
// we now create a text classification pipeline. It requires the path to the just downloader onnx model folder,
104107
// and a pipeline name
105-
sentimentPipeline, err := session.NewTextClassificationPipeline("/path/to/model/", "testPipeline")
108+
sentimentPipeline, err := session.NewTextClassificationPipeline(modelPath, "testPipeline")
106109
check(err)
107110
// we can now use the pipeline for prediction on a batch of strings
108111
batch := []string{"This movie is disgustingly good !", "The director tried too much"}
@@ -129,7 +132,7 @@ This will install the hugot binary at $HOME/.local/bin/hugot, and the correspond
129132
The if $HOME/.local/bin is on your $PATH, you can do:
130133

131134
```
132-
hugot run --model=/path/to/onnx/model --input=/path/to/input.jsonl --output=/path/to/folder/output --type=textClassification
135+
hugot run --model=KnightsAnalytics/distilbert-base-uncased-finetuned-sst-2-english --input=/path/to/input.jsonl --output=/path/to/folder/output --type=textClassification
133136
```
134137

135138
Hugot will load the model, process the input, and write the results in the output folder.
@@ -157,6 +160,10 @@ echo '{"input":"The director tried too much"}' | hugot run --model=/path/to/mode
157160

158161
To be able to run transformers fully from the command line.
159162

163+
Note that the --model parameter can be:
164+
1. the full path to a model to load
165+
2. the name of a huggingface model. Hugot will first try to look for the model at $HOME/hugot, or will try to download the model from huggingface.
166+
160167
## Contributing
161168

162169
### Development environment

cmd/main.go

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"os"
1111
"path"
1212
"path/filepath"
13+
"strings"
1314
"sync"
1415

1516
"github.com/knights-analytics/hugot"
@@ -25,6 +26,7 @@ var outputPath string
2526
var pipelineType string
2627
var sharedLibraryPath string
2728
var batchSize int
29+
var modelsDir string
2830

2931
var runCommand = &cli.Command{
3032
Name: "run",
@@ -34,7 +36,8 @@ var runCommand = &cli.Command{
3436
ArgsUsage: `
3537
--input: path to a .jsonl file or a folder with .jsonl files to process. If omitted, the input will be read from stdin.
3638
--output: path to a folder where to write the output. If omitted, the output will be sent to stdout.
37-
--model: path to the .onnx model to load.
39+
--model: model name or path to the .onnx model to load. The hugot cli looks for models with this chain: first use the provided path. If the path does not exist, look for a model
40+
with this name at $HOME/hugot/models. Finally, try to download the model from Huggingface and use it.
3841
--type: pipeline type. Currently implemented types are: featureExtraction, tokenClassification, and textClassification (only single label)
3942
--onnxruntimeSharedLibrary: path to the onnxruntime.so library. If not provided, the cli will try to load it from $HOME/lib/hugot/onnxruntime.so, and from /usr/lib/onnxruntime.so in the last instance.
4043
`,
@@ -80,11 +83,27 @@ var runCommand = &cli.Command{
8083
Required: false,
8184
Value: 20,
8285
},
86+
&cli.StringFlag{
87+
Name: "modelFolder",
88+
Usage: "Folder where to store downloaded models. Falls back to $HOME/hugot/models if not specified",
89+
Aliases: []string{"f"},
90+
Destination: &modelsDir,
91+
Required: false,
92+
Value: "",
93+
},
8394
},
8495
Action: func(ctx *cli.Context) error {
8596

8697
var onnxLibraryPathOpt hugot.SessionOption
8798

99+
if modelsDir == "" {
100+
userDir, err := os.UserHomeDir()
101+
if err != nil {
102+
return err
103+
}
104+
modelsDir = util.PathJoinSafe(userDir, "hugot", "models")
105+
}
106+
88107
if sharedLibraryPath != "" {
89108
onnxLibraryPathOpt = hugot.WithOnnxLibraryPath(sharedLibraryPath)
90109
} else {
@@ -110,6 +129,36 @@ var runCommand = &cli.Command{
110129

111130
var pipe pipelines.Pipeline
112131

132+
// is the model a full path to a model
133+
ok, err := util.FileSystem.Exists(ctx.Context, modelPath)
134+
if err != nil {
135+
return err
136+
}
137+
if !ok {
138+
// is the model the name of a model previously downloaded
139+
downloadedModelName := strings.Replace(modelPath, "/", "_", -1)
140+
ok, err = util.FileSystem.Exists(ctx.Context, util.PathJoinSafe(modelsDir, downloadedModelName))
141+
if err != nil {
142+
return err
143+
}
144+
if ok {
145+
modelPath = util.PathJoinSafe(modelsDir, downloadedModelName)
146+
} else {
147+
// is the model the name of a model to download
148+
if strings.Contains(modelPath, ":") {
149+
return fmt.Errorf("filters with : are currently not supported")
150+
}
151+
err = util.FileSystem.Create(context.Background(), modelsDir, os.ModePerm, true)
152+
if err != nil {
153+
return err
154+
}
155+
modelPath, err = session.DownloadModel(modelPath, modelsDir, hugot.NewDownloadOptions())
156+
if err != nil {
157+
return err
158+
}
159+
}
160+
}
161+
113162
switch pipelineType {
114163
case "tokenClassification":
115164
pipe, err = session.NewTokenClassificationPipeline(modelPath, "cliPipeline")

0 commit comments

Comments
 (0)