RTX Neural Shading (RTXNS)

repository·main·Indexed 43 days ago

https://github.com/nvidia-rtx/rtxns

A framework and set of examples for integrating machine learning models into graphics pipelines using the Slang shading language and GPU-accelerated ML extensions. It supports training and inference on Windows (via DirectX Preview Agility SDK) and Linux (via Vulkan VK_NV_cooperative_vector), providing tools for neural network management, weight conversion to device-optimal layouts, and Slang AutoDiff integration.

Tokens
32.5K
Snippets
75
Records
97
Agent score
55%

What's inside RTXNS

  1. Overview of RTX Neural Shading (RTXNS)

    main

    RTX Neural Shading (RTXNS) is a development starting point for integrating machine learning (ML) into graphics applications on Windows and Linux. It provides examples for training neural networks and performing inference alongside conventional graphics rendering.

    Key technologies used:

    • Slang: A shading language used for the implementation.
    • GPU ML Acceleration: Accessed via the DirectX Preview Agility SDK (Windows) or the Vulkan VK_NV_cooperative_vector extension (Windows and Linux).
    • SlangPy: Enables rapid neural-network development in Python, which can then be integrated into RTXNS for inference.

    Note: Users are expected to be familiar with ML and neural network concepts.

  2. Overview of RTXNS Samples

    main

    RTXNS provides several samples for different stages of neural shader development:

    • Simple Inferencing: Demonstrates implementing an inference shader using low-level building blocks to approximate a Disney BRDF shader.
    • Simple Training: Introduces training a neural network (e.g., replicating a transformed texture).
    • Shader Training: Extends training techniques using Slang's AutoDiff functionality through a multilayer perceptron (MLP) abstraction.
    • SlangPy Training: Shows how to create and train network architectures in Python using SlangPy for rapid experimentation.
    • SlangPy Inferencing: Demonstrates a workflow of prototyping in Python with SlangPy and transitioning the same implementation to C++ for production.
  3. How RTXNS neural modules and training work

    main

    RTXNS uses a modular architecture for neural networks, similar to PyTorch's torch.nn.Module. The core abstraction is the IModule interface, which defines a differentiable forward method.

    Modules are combined into architectures using a pattern analogous to torch.nn.Sequential. This allows for generic, reusable training functions that can operate on any model conforming to a specific IModule signature (e.g., a specific number of inputs and outputs) without knowing the internal architecture.

    Training typically follows these steps:

    1. Generate random input (e.g., UV coordinates).
    2. Sample a target value (e.g., from a reference texture).
    3. Evaluate the model using a differentiable forward method.
    4. Compute loss gradient.
    5. Backpropagate the gradient through the network parameters using bwd_diff.
    interface IModule<T : __BuiltinFloatingPointType, let NumInputs : int, let NumOutputs : int>
    {
        [BackwardDifferentiable]
        CoopVec<T, NumOutputs> forward(CoopVec<T, NumInputs> inputParams);
    }
  4. How SlangPy and C++ implementations relate

    main

    The SlangPy workflow is designed for a Prototype-to-Deployment pattern.

    1. Prototyping (Python/SlangPy): Use Python for rapid iteration, leveraging Slang's autodiff and Python's dynamic ecosystem to experiment with neural network configurations.
    2. Deployment (C++): Once the network is finalized, move the same Slang code to a C++ application.

    Because the core neural network logic resides in .slang files, you can maintain a single source of truth. The primary difference between the two environments is the resource binding: Python uses SlangPy bindings to map Python objects to Slang structs, while C++ uses direct engine bindings. This allows you to achieve C++ performance in production without rewriting the underlying neural network logic.

  5. Accumulate Loss Components with the LossAccumulation Module

    main

    The LossAccumulation module provides a GPU-friendly system for accumulating floating-point loss components into a RWByteAddressBuffer.

    LossConfig

    Describes how loss components are stored:

    • componentCount: Number of components to accumulate (clamped to LOSS_ACCUM_MAX_COMPONENTS, which is 128).
    • baseByteOffset: Starting byte offset in the target RWByteAddressBuffer.

    Key Functions

    • AtomicAddF32Portable: Automatically selects between native InterlockedAddF32 or a CAS-based fallback (AtomicAddFloat) for compatibility.
    • Zero<N>: Initializes an array of $N$ elements to 0.0f.
    • AddInPlace<N>: Adds src[N] into dst[N] for the first componentCount elements.
    • AccumulateComponents<N>: Performs atomic floating-point addition of an array of $N$ components into the global buffer at config.baseByteOffset.

    Typical Workflow

    1. Maintain per-thread or per-sample component arrays.
    2. Perform local reduction.
    3. Call AccumulateComponents to merge local results into the global GPU buffer.
    public struct LossConfig
    {
        uint componentCount;
        uint baseByteOffset;
        public uint GetComponentCount() { return min(componentCount, LOSS_ACCUM_MAX_COMPONENTS); }
    }
    
    // Accumulation usage
    AccumulateComponents<MAX_COMPONENTS>(accumulationBuffer, gLossShared[0], lossConfig);
  6. Understand MatrixLayout types and lifecycle

    main

    The library uses rtxns::MatrixLayout to manage how weights and biases are organized. Layouts are categorized into two types:

    1. Host Layouts (rtxns::MatrixLayout::RowMajor, rtxns::MatrixLayout::ColumnMajor): Hardware and API agnostic. Use these for storing networks to disk or sharing between different GPUs/APIs.
    2. Device Optimal Layouts (rtxns::MatrixLayout::InferencingOptimal, rtxns::MatrixLayout::TrainingOptimal): Opaque, hardware-specific formats. These often include specific alignment and padding for performance and should not be transferred between different GPUs.

    Typical Lifecycle:

    1. Initialize in a Host Layout (new or from file).
    2. Upload to GPU and convert to rtxns::MatrixLayout::TrainingOptimal using rtxns::NetworkUtilities::ConvertWeights.
    3. After training, convert to rtxns::MatrixLayout::InferencingOptimal for deployment.
    4. To save, convert back to a Host Layout before writing to file.
  7. Run Neural Networks with InferenceMLP or TrainingMLP

    main

    The MLP module provides two primary structures for executing neural networks depending on your use case.

    InferenceMLP

    Used for inference-only workloads. It provides a forward pass function but does not support backpropagation.

    TrainingMLP

    Used for training workloads. It provides both a forward pass and a backward pass. Unlike the manual LinearOp_Backward, TrainingMLP uses Slang's auto-differentiation functionality to automatically generate the backward propagation function.

    Both structures require specifying the floating-point type T, number of HIDDEN_LAYERS, INPUTS, HIDDEN layer size, OUTPUTS size, and the matrixLayout / componentType for the weights.

    // Inference usage pattern
    CoopVec<T, OUTPUTS> forward<Act, FinalAct>(CoopVec<T, INPUTS> inputParams, Act act, FinalAct finalAct);
    
    // Training usage pattern
    void backward<Act, FAct>(CoopVec<T, INPUTS> ip, Act act, FAct fact, CoopVec<T, OUTPUTS> loss);
  8. Project Structure Overview

    main

    Understanding the repository layout:

    DirectoryDetails
    /assetsAsset files used by samples
    /docsDocumentation for showcased technologies
    /samplesSamples demonstrating usage of MLPs
    /external/donutThe framework used for the examples
    /externalHelper dependencies for the examples
    /srcHelper and utility functions
  9. Chain multiple IModules using ModuleChain

    main

    To create deep architectures where the output of one module serves as the input to another, use the ModuleChain type. ModuleChain implements the IModule interface by composing two existing modules.

    ModuleChain requires:

    • T: The floating point type.
    • NumInputs: The input dimension of the first module.
    • NumHidden: The output dimension of the first module (which must match the input dimension of the second).
    • NumOutputs: The output dimension of the second module.
    • First: An IModule<T, NumInputs, NumHidden>.
    • Second: An IModule<T, NumHidden, NumOutputs>.

    To chain more than two modules (e.g., modules A, B, and C), you must nest the ModuleChain type. You first create a chain of A and B, then create a second chain using the result of the first as the First module and C as the Second module.

    struct ModuleChain<
        T : __BuiltinFloatingPointType,
        let NumInputs : int,
        let NumHidden : int,
        let NumOutputs : int,
        First : IModule<T, NumInputs, NumHidden>,
        Second : IModule<T, NumHidden, NumOutputs>
    > : IModule<T, NumInputs, NumOutputs>
    {
        First first;
        Second second;
    
        [BackwardDifferentiable]
        CoopVec<T, NumOutputs> forward(CoopVec<T, NumInputs> inputParams)
        {
            CoopVec<T, NumHidden> middle = first.forward(inputParams);
            return second.forward(middle);
        }
    }
  10. Understand the RTXNS Shader Training Flow

    main

    Training a neural network with RTXNS involves a multi-stage pipeline that transitions from host-side configuration to GPU-based training and inference. The process follows these steps:

    1. Host Initialization: Create host-side neural network storage and initialize it.
    2. Layout Creation: Create a device-optimal layout and allocate a GPU buffer.
    3. Layout Conversion: Convert the host-side network layout to the device-optimal layout on the GPU.
    4. Auxiliary Buffers: Create buffers for loss gradients and the optimizer pass.
    5. Training Loop: Execute batches of the training shader followed by the optimizer shader on random inputs to adjust weights based on loss.
    6. Inference: Render the output using an inference pixel shader (e.g., using the trained model to approximate a complex BRDF).
  11. Implement the Training Loop in Application Code

    main

    The training loop executes both a training pass and an optimizer pass multiple times per frame to accelerate convergence. In the provided example, BATCH_COUNT (defined in NetworkConfig.h, typically 100) determines how many iterations occur per frame.

    1. Training Pass: Dispatches a compute shader to calculate forward/backward passes and gradients.
    2. Optimizer Pass: Dispatches a compute shader (e.g., Adam) to update weights using the calculated gradients.

    Example loop structure:

    for (int i = 0; i < BATCH_COUNT; ++i)
    {
        nvrhi::ComputeState state;
        ...
        // Training pass
        state.bindings = { m_trainingPass.bindingSet };
        state.pipeline = m_trainingPass.pipeline;
        ...
        m_commandList->setComputeState(state);
        m_commandList->dispatch(m_batchSize / THREADS_PER_GROUP_TRAIN, 1, 1);
        ...
        // Optimizer pass
        state.bindings = { m_optimizerPass.bindingSet };
        state.pipeline = m_optimizerPass.pipeline;
        ...
        m_commandList->setComputeState(state);
        m_commandList->dispatch(div_ceil(m_totalParameterCount, THREADS_PER_GROUP_OPTIMIZE), 1, 1);
        ...
    }
  12. Vectorized initialization of Slang RNG instances

    main

    To provide a unique random number generator (RNG) state for every thread in a batch, you can pass an array of seeds to a Slang constructor. If the constructor expects a single uint but receives an ndarray of uints, SlangPy vectorizes the call, returning an NDBuffer of RNG instances with the same shape as the input seeds.

    # 1. Load the module containing the RNG struct
    module = Module.load_from_file(device, "SlangpyTraining.slang")
    
    # 2. Create a random seed array using NumPy
    pcg = np.random.PCG64(seed=12345)
    seeds = pcg.random_raw(batch_shape).astype(np.uint32)
    
    # 3. Call the vectorized constructor
    rng = module.RNG(seeds)