hugot

repository·main·Indexed 20 days ago

https://github.com/knights-analytics/hugot

A Go library for running ONNX-based transformer pipelines for inference and training, designed as a lift-and-shift solution for Hugging Face models. It supports hardware acceleration via CPU, GPU (CUDA), and TPU using pluggable backends including Native Go, Onnx Runtime (ORT), and OpenXLA (XLA). Hugot provides high-level APIs for various pipelines such as text classification, text generation, object detection, and question answering.

Tokens
8.6K
Snippets
26
Records
36
Agent score
71%

What's inside hugot

  1. What is Hugot?

    main

    Hugot is a Go library for running ONNX Transformer pipelines for inference and training. It is designed to be a 'lift-and-shift' solution for developers who want to use models trained with Hugging Face Python libraries directly in Go applications without the overhead of a Python RPC service or REST API.

    Key features:

    • Hugging Face Compatibility: Models exported to ONNX provide identical predictions to the Python version.
    • Hardware Acceleration: Supports CPU, TPU, and NVIDIA GPUs (via CUDA).
    • Pluggable Backends: Supports Native Go, Onnx Runtime (ORT), and OpenXLA (XLA).
    • Simplicity: Provides high-level pipeline APIs so you don't have to write custom inference or training code.
  2. How Hugot pipelines and sessions are structured

    main

    Hugot uses a Session to manage and retrieve pipelines. The core workflow involves creating a session, initializing pipelines via standalone functions, and retrieving them by alias.

    Core Components

    • Session struct: Created via NewSession, this object acts as a registry that holds all created pipelines, mapping them to name aliases.
    • NewPipeline function: A standalone generic function used to create a new pipeline and register it within a Session. It takes a session object and a pipeline configuration struct as input.
    • GetPipeline function: Used to retrieve a specific pipeline from a Session using its assigned alias.

    Pipeline Abstractions

    Every pipeline is built upon two primary components defined in pipelines/pipeline.go:

    1. BasePipeline struct: Provides common attributes (like ModelPath) and shared methods (like loadModel()). Specific pipeline implementations should embed this struct.
    2. The pipeline interface: Defines the required behavior for all pipelines. Any new pipeline must implement this interface.

    To implement a new pipeline type, you should create a new struct, embed BasePipeline, implement the pipeline interface methods, and update the Session and NewPipeline/GetPipeline logic in hugot.go to support the new type.

    // Conceptual workflow:
    // 1. session := NewSession()
    // 2. pipeline := NewPipeline[MyNewPipeline](session, config)
    // 3. retrieved := GetPipeline[MyNewPipeline](session, "alias")
  3. Choose a Hugot Backend

    main

    Hugot uses pluggable backends for tokenization and model execution. Choose based on your performance and hardware requirements:

    BackendBuild TagCapabilities
    Native Go(default)Simple workloads, environments disallowing cgo, small models (e.g., all-MiniLM-L6-v2). Best for small batches (~32 inputs).
    Onnx Runtime (ORT)-tags ORTFastest for CPU inference. Supports all pipelines, including generative (textGeneration). Does not support training.
    OpenXLA (XLA)-tags XLARequired for fine-tuning (e.g., embedding models). Only backend supporting TPUs. Does not support generative pipelines.

    Note on CUDA: To use NVIDIA GPUs, you must use a C backend (either ORT or XLA).

  4. Install and Configure Backends

    main

    Onnx Runtime (ORT)

    • Build Tag: -tags ORT or -tags ALL.
    • Dependency: Requires libonnxruntime.so.
    • Setup: By default, Hugot looks for the library at /usr/lib/libonnxruntime.so. You can specify a custom path using WithOnnxLibraryPath() in NewORTSession().

    OpenXLA (XLA)

    • Build Tag: -tags XLA or -tags ALL.
    • Setup: Install the XLA backend via the pjrt_installer:
      GOPROXY=direct go run github.com/gomlx/go-xla/cmd/pjrt_installer@latest -plugin=linux -version=v${GOPJRT_VERSION} -path=/usr/local/lib/go-xla

    Tokenizers (Required for XLA and ORT)

    • Dependency: Requires tokenizers.a (Rust-based).
    • Setup: Place tokenizers.a at /usr/lib/tokenizers.a or specify the directory via the CGO_LDFLAGS environment variable.

    Tip: Use the official Hugot Docker image to have all dependencies pre-installed.

  5. Configure Nvidia GPU acceleration with ONNX Runtime

    main

    To use Hugot with Nvidia GPU acceleration via ONNX Runtime, ensure you have the Nvidia driver installed and the CUDA GPU version of ONNX Runtime available on your system. You must also have compatible CUDA and cuDNN libraries installed (e.g., for ONNX Runtime 1.28.0, use CUDA 13.x and cuDNN 9.x).

    On Linux (e.g., awslinux/fedora), you can minimize the installation size by installing only these specific libraries:

    • cuda-cudart-13-3
    • libcublas-13-3
    • libcurand-13-3
    • libcufft-13-3
    • libcudnn9-cuda-13

    Note: libcufft and libcudnn9 are lazy-loaded and may be skippable depending on your models.

    ctx := context.Background()
    opts := []options.WithOption{
      options.WithCuda(map[string]string{
        "device_id": "0",
      }),
    }
    session, err := NewORTSession(ctx, opts...)
  6. Set up a development environment using Docker

    main

    The recommended way to develop with Hugot is using a Docker container pre-configured with tokenizer and onnxruntime libraries. This ensures all necessary dependencies and test models are available.

    Using Make

    From the root of the repository, use the following commands:

    1. Start the environment: Downloads test models, builds the container, and launches it (mounting your source code to /home/testuser/repositories/hugot).
      make start-dev-container
    2. Stop the environment: Tears down the container.
      make stop-dev-container

    Using VS Code Remote

    You can attach to the container using the VS Code Remote extension. Use the following configuration for your devcontainer settings:

    {
        "remoteUser": "testuser",
        "workspaceFolder": "/home/testuser/repositories/hugot",
        "extensions": [
    		"bierner.markdown-preview-github-styles",
    		"golang.go",
    		"ms-azuretools.vscode-docker"
    	],
        "remoteEnv": {"GOPATH": "/home/testuser/go"}
    }

    Bare Metal Requirements

    If you choose not to use Docker, you must manually install the following libraries to your system:

    • tokenizers.a located at /usr/lib/tokenizers.a
    • onnxruntime.so located at /usr/lib/onnxruntime.so
    make start-dev-container
  7. Fine-tune FeatureExtractionPipeline using XLA

    main

    Hugot supports training and fine-tuning for the FeatureExtractionPipeline. This requires building Hugot with XLA enabled, as it uses goMLX to convert ONNX models to XLA for training before serializing them back to ONNX.

    This is commonly used to fine-tune vector embeddings for semantic textual similarity (e.g., for RAG).

    Training Data Format

    Your training dataset must be in JSONL format with the following structure:

    {"sentence1": "text", "sentence2": "text", "score": 0.0}

    Where score is a float between 0 and 1 representing semantic similarity.

    Loss Functions

    By default, a cosine similarity loss is used. You can specify alternative loss functions from goMLX by configuring the XLATrainingOptions field within the TrainingConfig struct.

    {"sentence1": "The quick brown fox jumps over the lazy dog", "sentence2": "A quick brown fox jumps over a lazy dog", "score": 1}
    {"sentence1": "The quick brown fox jumps over the lazy dog", "sentence2": "A quick brown cow jumps over a lazy caterpillar", "score": 0.5}
  8. Optimize ONNX Runtime throughput

    main

    By default, ONNX Runtime is optimized for latency. To maximize throughput, call a single shared Hugot pipeline from multiple goroutines (ideally 1 per CPU core) using a channel to pass input data.

    To achieve this, configure the session with the following options to restrict each goroutine to a single core and reduce memory overhead:

    • WithInterOpNumThreads(1)
    • WithIntraOpNumThreads(1)
    • WithCpuMemArena(false)
    • WithMemPattern(false)
    session, err := hugot.NewORTSession(
    	context.Background(),
    	hugot.WithInterOpNumThreads(1),
    	hugot.WithIntraOpNumThreads(1),
    	hugot.WithCpuMemArena(false),
    	hugot.WithMemPattern(false),
    )
  9. Use object stores with Hugot via abstract file systems

    main

    Hugot uses an abstract file system (afs). While it works with standard OS filesystems out of the box, you must import the appropriate plugin to use object stores like S3.

    import _ "github.com/viant/afsc/s3"
  10. Run the Hugot test suite

    main

    To ensure your changes are correct and maintain test coverage (which must not dip below 80%), run the full test suite using make. This command builds a test image and executes all tests within a container.

    After execution, test results are stored in a testTarget folder created in your source directory.

    make clean run-tests
  11. Configure Nvidia GPU acceleration with OpenXLA

    main

    To use Hugot with Nvidia GPU acceleration via OpenXLA, first install CUDA support using the pjrt_installer tool:

    GOPROXY=direct go run github.com/gomlx/go-xla/cmd/pjrt_installer@latest -plugin=cuda13 -version=${JAX_CUDA_VERSION} -path=/usr/local/lib/go-xla

    Then, initialize the session in your Go code using NewXLASession with the WithCuda option.

    ctx := context.Background()
    opts := []options.WithOption{
      options.WithCuda(map[string]string{
        "device_id": "0",
      }),
    }
    session, err := NewXLASession(ctx, opts...)
  12. Manage Hugot pipelines with a Session

    main

    A Session is the primary entry point for managing the lifecycle of ONNX transformer pipelines and models. It handles model loading, prevents redundant loading of the same model, and ensures that resources are cleaned up when the session is destroyed.

    Key responsibilities of a Session:

    • Model Management: Automatically loads and caches models based on their path and ONNX filename. If multiple pipelines use the same model, the session ensures the model is only loaded once.
    • Pipeline Lifecycle: Allows creating (NewPipeline), retrieving (GetPipeline), and closing (ClosePipeline) specific pipelines.
    • Resource Cleanup: The Destroy() method tears down all pipelines, models, and the underlying ONNX runtime environment.

    It is highly recommended to call s.Destroy() using defer to prevent memory leaks.

    // Example of session lifecycle management
    session, err := hugot.NewSession(ctx, "XLA", hugot.WithOption(...))
    if err != nil {
    	log.Fatal(err)
    }
    defer session.Destroy()
    
    // Create a pipeline
    pipeline, err := hugot.NewPipeline(session, hugot.TextGenerationConfig{
    	Name: "my-gen-pipeline",
    	ModelPath: "/path/to/model",
    	OnnxFilename: "model.onnx",
    })