| |
|
|
| package compactonnx |
|
|
| import ( |
| "bytes" |
| "encoding/json" |
| "errors" |
| "fmt" |
| "io" |
| "math" |
| "os" |
| "path/filepath" |
| "runtime" |
| "strconv" |
| "sync" |
|
|
| "huggingface.co/turenlabs/Vigil/source/pkg/compactmodel" |
| ort "github.com/yalue/onnxruntime_go" |
| ) |
|
|
| var compactRuntime struct { |
| sync.Mutex |
| initialized bool |
| libraryPath string |
| version string |
| } |
|
|
| |
| type Model struct { |
| metadata Metadata |
| markers Markers |
| session *ort.AdvancedSession |
| input *ort.Tensor[float32] |
| output *ort.Tensor[float32] |
| mu sync.Mutex |
| closed bool |
| } |
|
|
| |
| |
| func Load(config Config) (*Model, error) { |
| pair, err := readArtifactPair(config.ModelPath, config.MetadataPath) |
| if err != nil { |
| return nil, err |
| } |
| metadata, err := decodeMetadata(pair.metadata) |
| if err != nil { |
| return nil, err |
| } |
| if err := validateMetadata(metadata, pair); err != nil { |
| return nil, err |
| } |
| runtimeVersion, err := initializeRuntime(config.RuntimeLibrary) |
| if err != nil { |
| return nil, err |
| } |
| if err := validateGraph(pair.model, metadata); err != nil { |
| return nil, err |
| } |
|
|
| input, err := ort.NewEmptyTensor[float32](ort.NewShape(1, compactmodel.TotalFeatures)) |
| if err != nil { |
| return nil, fmt.Errorf("create compact ONNX input: %w", err) |
| } |
| output, err := ort.NewEmptyTensor[float32](ort.NewShape(1, 1)) |
| if err != nil { |
| input.Destroy() |
| return nil, fmt.Errorf("create compact ONNX output: %w", err) |
| } |
| session, err := ort.NewAdvancedSessionWithONNXData( |
| pair.model, |
| []string{InputName}, |
| []string{OutputName}, |
| []ort.Value{input}, |
| []ort.Value{output}, |
| nil, |
| ) |
| if err != nil { |
| output.Destroy() |
| input.Destroy() |
| return nil, fmt.Errorf("create compact ONNX session: %w", err) |
| } |
|
|
| return &Model{ |
| metadata: metadata, |
| markers: Markers{ |
| ModelLoaded: true, |
| ModelType: "compact_hashed_linear_onnx", |
| ModelSHA256: pair.modelSHA256, |
| ModelSizeBytes: int64(len(pair.model)), |
| MetadataSHA256: pair.metadataSHA256, |
| PreprocessingContractSHA256: compactmodel.ContractSHA256(), |
| Runtime: "onnxruntime", |
| RuntimeVersion: runtimeVersion, |
| FallbackUsed: false, |
| }, |
| session: session, |
| input: input, |
| output: output, |
| }, nil |
| } |
|
|
| func decodeMetadata(data []byte) (Metadata, error) { |
| decoder := json.NewDecoder(bytes.NewReader(data)) |
| decoder.DisallowUnknownFields() |
| var metadata Metadata |
| if err := decoder.Decode(&metadata); err != nil { |
| return Metadata{}, fmt.Errorf("decode compact metadata: %w", err) |
| } |
| var trailing any |
| if err := decoder.Decode(&trailing); !errors.Is(err, io.EOF) { |
| if err == nil { |
| return Metadata{}, errors.New("decode compact metadata: trailing JSON value") |
| } |
| return Metadata{}, fmt.Errorf("decode compact metadata trailer: %w", err) |
| } |
| return metadata, nil |
| } |
|
|
| func validateMetadata(metadata Metadata, pair artifactPair) error { |
| if metadata.SchemaVersion != MetadataSchema { |
| return fmt.Errorf("compact metadata schema mismatch: got %q", metadata.SchemaVersion) |
| } |
| if metadata.Model.SHA256 != pair.modelSHA256 { |
| return errors.New("compact metadata model SHA-256 mismatch") |
| } |
| if metadata.Model.SizeBytes != int64(len(pair.model)) { |
| return errors.New("compact metadata model size mismatch") |
| } |
| if metadata.Model.SizeBytes <= 0 || metadata.Model.SizeBytes > MaxModelBytes { |
| return errors.New("compact metadata model size is outside the release limit") |
| } |
| if metadata.Model.Family != "hashed-word-char-linear" { |
| return errors.New("compact metadata model family mismatch") |
| } |
| if metadata.Model.InputName != InputName || metadata.Model.InputFeatures != compactmodel.TotalFeatures { |
| return errors.New("compact metadata input contract mismatch") |
| } |
| if metadata.Model.OutputName != OutputName || metadata.Model.OutputElements != 1 { |
| return errors.New("compact metadata output contract mismatch") |
| } |
| if err := compactmodel.ValidateMetadata(metadata.preprocessingMetadata()); err != nil { |
| return err |
| } |
| if math.IsNaN(metadata.Threshold) || math.IsInf(metadata.Threshold, 0) || metadata.Threshold <= 0 || metadata.Threshold >= 1 { |
| return errors.New("compact metadata threshold must be finite and between zero and one") |
| } |
| return nil |
| } |
|
|
| func validateGraph(data []byte, metadata Metadata) error { |
| inputs, outputs, err := ort.GetInputOutputInfoWithONNXData(data) |
| if err != nil { |
| return fmt.Errorf("inspect compact ONNX graph: %w", err) |
| } |
| if len(inputs) != 1 || !validTensor(inputs[0], InputName, compactmodel.TotalFeatures) { |
| return fmt.Errorf("compact ONNX input mismatch: want one float tensor %s [batch,%d]", InputName, compactmodel.TotalFeatures) |
| } |
| if len(outputs) != 1 || !validTensor(outputs[0], OutputName, 1) { |
| return fmt.Errorf("compact ONNX output mismatch: want one float tensor %s [batch,1]", OutputName) |
| } |
| modelMetadata, err := ort.GetModelMetadataWithONNXData(data) |
| if err != nil { |
| return fmt.Errorf("inspect compact ONNX metadata: %w", err) |
| } |
| defer modelMetadata.Destroy() |
| required := map[string]string{ |
| "model_family": "hashed-word-char-linear", |
| "word_features": strconv.Itoa(compactmodel.WordFeatures), |
| "char_features": strconv.Itoa(compactmodel.CharFeatures), |
| "structured_features": strconv.Itoa(compactmodel.StructuredFeatures), |
| "preprocessing": "sklearn-murmurhash3 word(1,2)+char(4)+package-structure-v1", |
| } |
| for key, want := range required { |
| got, found, err := modelMetadata.LookupCustomMetadataMap(key) |
| if err != nil || !found || got != want { |
| return fmt.Errorf("compact ONNX internal metadata mismatch for %s", key) |
| } |
| } |
| thresholdText, found, err := modelMetadata.LookupCustomMetadataMap("threshold") |
| if err != nil || !found { |
| return errors.New("compact ONNX internal threshold metadata is missing") |
| } |
| threshold, err := strconv.ParseFloat(thresholdText, 64) |
| if err != nil || threshold != metadata.Threshold { |
| return errors.New("compact ONNX internal threshold metadata mismatch") |
| } |
| return nil |
| } |
|
|
| func validTensor(info ort.InputOutputInfo, name string, width int) bool { |
| if info.Name != name || info.OrtValueType != ort.ONNXTypeTensor || info.DataType != ort.TensorElementDataTypeFloat { |
| return false |
| } |
| return len(info.Dimensions) == 2 && |
| (info.Dimensions[0] == -1 || info.Dimensions[0] == 1) && |
| info.Dimensions[1] == int64(width) |
| } |
|
|
| func initializeRuntime(explicit string) (string, error) { |
| compactRuntime.Lock() |
| defer compactRuntime.Unlock() |
| if compactRuntime.initialized { |
| if explicit != "" { |
| resolved, err := canonicalRuntimeLibrary(explicit) |
| if err != nil { |
| return "", err |
| } |
| if resolved != compactRuntime.libraryPath { |
| return "", errors.New("ONNX Runtime is already initialized from a different library") |
| } |
| } |
| return compactRuntime.version, nil |
| } |
| if ort.IsInitialized() { |
| return "", errors.New("ONNX Runtime was initialized outside the compact loader; exact runtime provenance is unavailable") |
| } |
| library := explicit |
| if library == "" { |
| library = os.Getenv("ONNXRUNTIME_LIB") |
| } |
| if library == "" { |
| library = discoverRuntimeLibrary() |
| } |
| if library == "" { |
| return "", errors.New("ONNX Runtime library is required; compact scoring has no fallback") |
| } |
| resolved, err := canonicalRuntimeLibrary(library) |
| if err != nil { |
| return "", err |
| } |
| ort.SetSharedLibraryPath(resolved) |
| if err := ort.InitializeEnvironment(); err != nil { |
| return "", fmt.Errorf("initialize ONNX Runtime: %w", err) |
| } |
| compactRuntime.initialized = true |
| compactRuntime.libraryPath = resolved |
| compactRuntime.version = ort.GetVersion() |
| return compactRuntime.version, nil |
| } |
|
|
| func canonicalRuntimeLibrary(path string) (string, error) { |
| if hasTraversal(path) { |
| return "", errors.New("ONNX Runtime path contains traversal") |
| } |
| abs, err := filepath.Abs(path) |
| if err != nil { |
| return "", fmt.Errorf("resolve ONNX Runtime path: %w", err) |
| } |
| resolved, err := filepath.EvalSymlinks(abs) |
| if err != nil { |
| return "", fmt.Errorf("resolve ONNX Runtime library: %w", err) |
| } |
| info, err := os.Lstat(resolved) |
| if err != nil { |
| return "", fmt.Errorf("inspect ONNX Runtime library: %w", err) |
| } |
| if info.Mode()&os.ModeSymlink != 0 || !info.Mode().IsRegular() { |
| return "", errors.New("ONNX Runtime library is not a regular file") |
| } |
| return resolved, nil |
| } |
|
|
| func discoverRuntimeLibrary() string { |
| name := "libonnxruntime.so" |
| switch runtime.GOOS { |
| case "darwin": |
| name = "libonnxruntime.dylib" |
| case "windows": |
| name = "onnxruntime.dll" |
| } |
| var candidates []string |
| if executable, err := os.Executable(); err == nil { |
| directory := filepath.Dir(executable) |
| candidates = append(candidates, filepath.Join(directory, name), filepath.Join(directory, "lib", name)) |
| } |
| if working, err := os.Getwd(); err == nil { |
| candidates = append(candidates, filepath.Join(working, name), filepath.Join(working, "lib", name)) |
| } |
| switch runtime.GOOS { |
| case "darwin": |
| candidates = append(candidates, filepath.Join("/opt/homebrew/lib", name), filepath.Join("/usr/local/lib", name)) |
| case "linux": |
| candidates = append(candidates, filepath.Join("/usr/lib", name), filepath.Join("/usr/local/lib", name), filepath.Join("/usr/lib/x86_64-linux-gnu", name), filepath.Join("/usr/lib/aarch64-linux-gnu", name)) |
| } |
| for _, candidate := range candidates { |
| if _, err := os.Stat(candidate); err == nil { |
| return candidate |
| } |
| } |
| return "" |
| } |
|
|
| |
| func (model *Model) Score(pkg compactmodel.Package) (float64, error) { |
| vector, err := compactmodel.Vectorize(pkg) |
| if err != nil { |
| return 0, err |
| } |
| model.mu.Lock() |
| defer model.mu.Unlock() |
| if model.closed || model.session == nil { |
| return 0, errors.New("compact ONNX model is closed") |
| } |
| copy(model.input.GetData(), vector) |
| if err := model.session.Run(); err != nil { |
| return 0, fmt.Errorf("run compact ONNX: %w", err) |
| } |
| values := model.output.GetData() |
| if len(values) != 1 { |
| return 0, fmt.Errorf("compact ONNX output length mismatch: got %d", len(values)) |
| } |
| probability := float64(values[0]) |
| if math.IsNaN(probability) || math.IsInf(probability, 0) || probability < 0 || probability > 1 { |
| return 0, errors.New("compact ONNX output is not a finite probability") |
| } |
| return probability, nil |
| } |
|
|
| func (model *Model) Threshold() float64 { return model.metadata.Threshold } |
| func (model *Model) Markers() Markers { return model.markers } |
|
|
| |
| |
| func (model *Model) Close() error { |
| model.mu.Lock() |
| defer model.mu.Unlock() |
| if model.closed { |
| return nil |
| } |
| model.closed = true |
| var errs []error |
| if model.session != nil { |
| errs = append(errs, model.session.Destroy()) |
| } |
| if model.output != nil { |
| errs = append(errs, model.output.Destroy()) |
| } |
| if model.input != nil { |
| errs = append(errs, model.input.Destroy()) |
| } |
| return errors.Join(errs...) |
| } |
|
|