interface IDifferentiable
Description
Represents a ‘value’ type that is differentiable for the purposes of automatic differentiation.
See the auto-diff user guide section for an introduction to differentiable value types (https://shader-slang.org/slang/user-guide/autodiff.html#differentiable-value-types)
Builtin Differentiable Value Types
The following built-in types are differentiable:
- Scalars: float, double and half.
- Vector/Matrix: vector and matrix of float, double and half types.
- Arrays: T[n] is differentiable if T is differentiable.
- Tuples: Tuple<each T> is differentiable if T is differentiable.
The IDifferentiable interface requires the following definitions (which can be auto-generated by the compiler for most scenarios)
interface IDifferentiable
{
associatedtype Differential : IDifferentiable
where Differential.Differential == Differential;
static Differential dzero();
static Differential dadd(Differential, Differential);
}
As defined by the IDifferentiable interface, a differentiable type must have a Differential associated type that stores the derivative of the value. A further requirement is that the type of the second-order derivative must be the same Differential type. In another words, given a type T, T.Differential can be different from T, but T.Differential.Differential must equal to T.Differential.
In addition, a differentiable type must define the zero value of its derivative, and how to add two derivative values together. These function are used during reverse-mode auto-diff, to initialize and accumulate derivatives of the given type.
Automatic Fulfillment of IDifferentiable Requirements
Assume the user has defined the following type:
struct MyRay
{
float3 origin;
float3 dir;
int nonDifferentiablePayload;
}
The type can be made differentiable by adding IDifferentiable conformance:
struct MyRay : IDifferentiable
{
float3 origin;
float3 dir;
int nonDifferentiablePayload;
}
Note that this code does not provide any explicit implementation of the IDifferentiable requirements. In this case the compiler will automatically synthesize all the requirements. This should provide the desired behavior most of the time. The procedure for synthesizing the interface implementation is as follows:
- A new type is generated that stores the Differential of all differentiable fields. This new type itself will conform to the IDifferentiable interface, and it will be used to satisfy the Differential associated type requirement.
- Each differential field will be associated to its corresponding field in the newly synthesized Differential type.
- The zero value of the differential type is made from the zero value of each field in the differential type.
- The dadd method invokes the dadd operations for each field whose type conforms to IDifferentiable.
- If the synthesized Differential type contains exactly the same fields as the original type, and the type of each field is the same as the original field type, then the original type itself will be used as the Differential type instead of creating a new type to satisfy the Differential associated type requirement. This means that all the synthesized Differential type use itself to meet its own IDifferentiable requirements.
Manual fulfilment of IDifferentiable requirements
In rare cases where more control is desired, the user can manually provide the implementation. To do so, we will first define the Differential type for MyRay, and use it to fulfill the Differential requirement in MyRay:
struct MyRayDifferential
{
float3 d_origin;
float3 d_dir;
}
struct MyRay : IDifferentiable
{
// Specify that `MyRay.Differential` is `MyRayDifferential`.
typealias Differential = MyRayDifferential;
// Specify that the derivative for `origin` will be stored in `MayRayDifferential.d_origin`.
[DerivativeMember(MayRayDifferential.d_origin)]
float3 origin;
// Specify that the derivative for `dir` will be stored in `MayRayDifferential.d_dir`.
[DerivativeMember(MayRayDifferential.d_dir)]
float3 dir;
// This is a non-differentiable field so we don't put any attributes on it.
int nonDifferentiablePayload;
// Define zero derivative.
static MyRayDifferential dzero()
{
return {float3(0.0), float3(0.0)};
}
// Define the add operation of two derivatives.
static MyRayDifferential dadd(MyRayDifferential v1, MyRayDifferential v2)
{
MyRayDifferential result;
result.d_origin = v1.d_origin + v2.d_origin;
result.d_dir = v1.d_dir + v2.d_dir;
return result;
}
}
Note that for each struct field that is differentiable, we need to use the [DerivativeMember] attribute to associate it with the corresponding field in the Differential type, so the compiler knows how to access the derivative for the field.
However, there is still a missing piece in the above code: we also need to make MyRayDifferential conform to IDifferentiable because it is required that the Differential of a type must itself be Differential. Again we can use automatic fulfillment by simply adding IDifferentiable conformance to MyRayDifferential:
struct MyRayDifferential : IDifferentiable
{
float3 d_origin;
float3 d_dir;
}
In this case, since all fields in MyRayDifferential are differentiable, and the Differential of each field is the same as the original type of each field (i.e. float3.Differential==float3 as defined in the core module), the compiler will automatically use the type itself as its own Differential, making MyRayDifferential suitable for use as Differential of MyRay.
We can also choose to manually implement IDifferentiable interface for MyRayDifferential as in the following code:
struct MyRayDifferential : IDifferentiable
{
typealias Differential = MyRayDifferential;
[DerivativeMember(MyRayDifferential.d_origin)]
float3 d_origin;
[DerivativeMember(MyRayDifferential.d_dir)]
float3 d_dir;
static MyRayDifferential dzero()
{
return {float3(0.0), float3(0.0)};
}
static MyRayDifferential dadd(MyRayDifferential v1, MyRayDifferential v2)
{
MyRayDifferential result;
result.d_origin = v1.d_origin + v2.d_origin;
result.d_dir = v1.d_dir + v2.d_dir;
return result;
}
}
In this specific case, the automatically generated IDifferentiable implementation will be exactly the same as the manually written code listed above.
Associated types
_Differential
Constraints:
- IDifferentiable.This.Differential : IDifferentiable