Hugging Face Kernels

repository·main·Indexed 20 days ago

https://github.com/huggingface/kernels

A project providing a mechanism for Python applications to dynamically load and execute optimized compute kernels from the Hugging Face Hub. It includes the `kernels` Python package for loading portable, unique, and compatible kernels, and `kernel-builder`, a Nix-based build system for creating Hub-compatible compute kernels. Supports multiple hardware backends including CUDA, ROCm, XPU, Metal, Huawei NPU, Neuron, and TPU.

Tokens
128.5K
Snippets
377
Records
510
Agent score
72%

What's inside huggingface-kernels

  1. What is kernel-builder

    main

    The kernel-builder is a build system designed to create Hub-compatible compute kernels. It manages the complexity of ensuring kernels are:

    • Portable: Kernels can be loaded from paths outside of PYTHONPATH.
    • Unique: Multiple versions of the same kernel can coexist in a single Python process.
    • Compatible: Supports recent Python versions and various PyTorch build configurations (including different CUDA versions and C++ ABIs).

    This builder uses Nix to build kernels via the PyTorch C++ Frontend. The resulting kernels can be loaded using the kernels Python package.

  2. Understand the Kernels project components

    main

    The project is split into two primary components depending on your goal:

    1. kernel-builder: Use this if you want to build, package, and distribute compute kernels. It provides utilities to ensure your kernels are compatible with the Hugging Face Hub and the kernels loading mechanism.
    2. kernels: Use this Python package if you want to load and use compatible compute kernels from the Hub in your own applications.
  3. The purpose and benefits of the kernels package

    main

    The kernels package provides a standardized ecosystem for structuring, building, distributing, and loading compute kernels. It aims to solve several common developer pain points:

    • Standardized Builds: Uses a builder component to take kernel source in a pre-defined layout with declarative configuration to produce compiled kernels for multiple backends (CUDA, ROCM, XPU), OSs, and architectures.
    • Reproducibility: Enforces consistent build environments and steps.
    • Efficient Distribution: Kernels are hosted on the Hugging Face Hub and loaded via the kernels Python package. This allows the package to automatically fetch the correct build for the user's specific system, avoiding lengthy local compilation times.
    • Version Management: Supports loading multiple versions of the same kernel to prevent dependency conflicts and ensures compatibility across different PyTorch versions and hardware capabilities.
    • Hub Integration: Leverages Hugging Face Hub features like XET for fast transfers, seamless versioning, and hardware compatibility visibility.
  4. Use the kernels CLI to manage compute kernels

    main

    The kernels CLI is the primary interface for managing compute kernels. It provides a suite of commands to download, describe, benchmark, and verify the integrity of kernels.

    Available command categories include:

    • Information & Versions: Use info to describe a kernel and versions to view available kernel versions.
    • Lifecycle & Integrity: Use download to fetch kernels, lock to pin specific kernel revisions, and verify-signature to ensure kernel authenticity.
    • Performance: Use benchmark to run and view performance results for a specific kernel.
  5. Manage LDS (Local Data Share) budget on MI355X

    main

    MI355X provides 160 KB of LDS per CU, which is 2.5x more than the MI300X. This allows for higher num_stages in Triton kernels.

    LDS Usage Formula: LDS usage = (BLOCK_M × BLOCK_K × dtype_size + BLOCK_K × BLOCK_N × dtype_size) × num_stages

    Recommended Stage Configuration based on budget:

    • < 80 KB: 2-3 stages
    • 80-160 KB: 2 stages
    • > 160 KB: 1 stage (or reduce block sizes)
  6. Follow the core CPU kernel file structure

    main

    Every CPU kernel implementation should follow this directory and file organization to ensure proper compilation and Python integration:

    my_kernel/
    ├── my_kernel_cpu/
    │   ├── cpu_features.hpp          # CPUID detection (in its own namespace)
    │   ├── my_kernel_cpu.cpp         # Dispatcher (handles AVX512 vs fallback)
    │   ├── my_kernel_cpu.hpp         # Shared declarations
    │   ├── my_kernel_cpu_torch.cpp   # Python ↔ C++ bridge
    │   ├── my_kernel_avx512.cpp      # AVX512 implementation
    │   └── my_kernel_avx512.hpp      # AVX512 declarations
    ├── torch-ext/
    │   └── torch_binding.cpp         # PyTorch operator registration
    └── build.toml                    # Multi-target compilation config
    my_kernel/
    ├── my_kernel_cpu/
    │   ├── cpu_features.hpp          # CPUID detection (own namespace)
    │   ├── my_kernel_cpu.cpp         # Dispatcher
    │   ├── my_kernel_cpu.hpp         # Shared declarations
    │   ├── my_kernel_cpu_torch.cpp   # Python ↔ C++ bridge
    │   ├── my_kernel_avx512.cpp      # AVX512 implementation
    │   └── my_kernel_avx512.hpp      # AVX512 declarations
    ├── torch-ext/
    │   └── torch_binding.cpp         # Op registration
    └── build.toml                    # Multi-target compilation
  7. Optimize CUDA Kernel Performance

    main

    Common performance bottlenecks in CUDA kernels and their solutions:

    • Bank Conflicts: Avoid shared memory bank conflicts by adding padding to arrays (e.g., use [32][33] instead of [32][32]).
    • Poor Occupancy: Check register usage using nvcc --ptxas-options=-v your_kernel.cu to ensure high SM utilization.
    • Memory Coalescing: Ensure memory accesses are 128-byte aligned to maximize bandwidth utilization.
  8. Implement Register-Spill-Aware Autotune Pruning

    main

    To avoid slow double-compile/benchmark cycles on XPU, prune autotune configurations that are likely to cause register spills. Use an early_config_prune function to estimate per-thread GRF pressure using the following model:

    regs ~= (accumulators * BM * BN + stages * (BM * BK + weight_tiles * BN * BK)) / (num_warps * warp_size)

    Implementation Guidelines:

    • Apply only to XPU backends.
    • BM, BN, and BK should be read from autotune config kwargs or launch args.
    • Accumulator count: Usually 2 for gate/up fused kernels, 1 for down/simple GEMM.
    • Weight tile count: Usually 2 for gate/up fused kernels, 1 for down/simple GEMM.
    • Stages: Use num_stages for pipelined MMA paths; use 1 for scalar/non-pipelined paths.
    • Always keep a fallback configuration (e.g., the one with the lowest estimated register pressure).
    • Use this as a coarse filter for catastrophic spillers, not a perfect predictor.
  9. Understand the kernel repository directory layout

    main

    A compliant kernel repository must follow a specific directory structure to ensure compatibility across different environments:

    1. build/ directory: Contains build variants named using the template <framework><version>-cxx<abiver>-<cu><cudaver>-<arch>-<os> (e.g., build/torch26-cxx98-cu118-x86_64-linux).
    2. Variant directory: Each variant directory must contain an __init__.py file.
    3. Compatibility directory: For older versions of the kernels package, each variant directory must also contain a sub-directory named after the repository (with dashes replaced by underscores). This directory must also contain an __init__.py that exports the same symbols as the variant's __init__.py.
  10. Core CUDA Kernel Patterns for H100

    main

    When writing optimized kernels for H100 (SM 9.0), use these patterns for performance:

    Vectorized Memory Access

    Use specialized types to load multiple elements in a single instruction:

    • BFloat16: Use __nv_bfloat162 for 32-bit loads.
    • FP16: Use __half2.
    • FP32: Use float4.

    Warp Shuffle Reductions

    Use __shfl_xor_sync for efficient intra-warp reductions.

    Thread Configuration

    • Element-wise (RoPE, GEGLU): Use a standard BLOCK_SIZE (e.g., 256).
    • Reductions (LayerNorm, RMSNorm): Ensure threads are rounded to a warp boundary and account for vectorized access (e.g., hidden_size / 2 for bf16/fp16).
    // BFloat16 vectorization example
    const __nv_bfloat162* vec_input = reinterpret_cast<const __nv_bfloat162*>(row_input);
    
    #pragma unroll 4
    for (int i = tid; i < vec_hidden; i += stride) {
        __nv_bfloat162 v = vec_input[i];
        float v0 = __bfloat162float(v.x);
        float v1 = __bfloat162float(v.y);
        sum_sq += v0 * v0 + v1 * v1;
    }
    
    // Warp shuffle reduction example
    template <typename T>
    __device__ __forceinline__ T warp_reduce_sum(T val) {
        #pragma unroll
        for (int offset = 16; offset > 0; offset >>= 1) {
            val += __shfl_xor_sync(0xffffffff, val, offset);
        }
        return val;
    }
  11. Achieve manylinux_2_28 compatibility

    main

    To ensure manylinux_2_28 compatibility, the builder uses a toolchain based on the GCC toolsets from AlmaLinux 8.

    Key implementation details:

    • Toolchain Repackaging: AlmaLinux 8 toolsets and libstdc++ are repackaged as Nix derivations.
    • GCC Construction: Various toolset packages are merged into an unwrapped GCC, which is then wrapped with binutils and gcc to create a stdenv.
    • Custom glibc: The builder does not reuse the AlmaLinux glibc because its dynamic loader contains hardcoded FHS paths (like /lib64) that are invalid in the Nix store. Instead, a custom glibc 2.28 package is built specifically for this purpose (defined in nix-builder/pkgs/manylinux_2_28/stdenv.nix).
  12. Register Torch extension functions with unique namespaces

    main

    When creating Torch native extension functions, they must be registered in torch.ops.<namespace>. To prevent clashes when multiple versions of the same kernel are loaded into a single Python process, the namespace must be unique for each version.

    Recommended strategies for ensuring unique namespaces:

    • Append a truncated SHA-1 hash of the git commit used for the build.
    • Append random material to the name.

    Warning: Do not use version numbers or git tags for the namespace, as they are not guaranteed to be unique across different commits or stable enough for uniqueness.