diff --git a/stdlib/private/CMakeLists.txt b/stdlib/private/CMakeLists.txt index 45cf31363574b..46fff31994d92 100644 --- a/stdlib/private/CMakeLists.txt +++ b/stdlib/private/CMakeLists.txt @@ -5,6 +5,9 @@ endif() if(SWIFT_BUILD_SDK_OVERLAY) # SwiftPrivateThreadExtras makes use of Darwin/Glibc, which is part of the # SDK overlay. It can't be built separately from the SDK overlay. + if(SWIFT_ENABLE_EXPERIMENTAL_DIFFERENTIABLE_PROGRAMMING) + add_subdirectory(DifferentiationUnittest) + endif() add_subdirectory(RuntimeUnittest) add_subdirectory(StdlibUnicodeUnittest) add_subdirectory(StdlibCollectionUnittest) diff --git a/stdlib/private/DifferentiationUnittest/CMakeLists.txt b/stdlib/private/DifferentiationUnittest/CMakeLists.txt new file mode 100644 index 0000000000000..bb6284b191310 --- /dev/null +++ b/stdlib/private/DifferentiationUnittest/CMakeLists.txt @@ -0,0 +1,7 @@ +add_swift_target_library(swiftDifferentiationUnittest ${SWIFT_STDLIB_LIBRARY_BUILD_TYPES} IS_STDLIB + # This file should be listed first. Module name is inferred from the filename. + DifferentiationUnittest.swift + + SWIFT_MODULE_DEPENDS _Differentiation StdlibUnittest + INSTALL_IN_COMPONENT stdlib-experimental + DARWIN_INSTALL_NAME_DIR "${SWIFT_DARWIN_STDLIB_PRIVATE_INSTALL_NAME_DIR}") diff --git a/stdlib/private/DifferentiationUnittest/DifferentiationUnittest.swift b/stdlib/private/DifferentiationUnittest/DifferentiationUnittest.swift new file mode 100644 index 0000000000000..f24dd18a7925e --- /dev/null +++ b/stdlib/private/DifferentiationUnittest/DifferentiationUnittest.swift @@ -0,0 +1,304 @@ +//===--- DifferentiationUnittest.swift ------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2020 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +@_exported import _Differentiation +import StdlibUnittest + +public enum _GlobalLeakCount { + public static var count = 0 +} + +/// Execute body and check expected leak count. +public func withLeakChecking( + expectedLeakCount: Int = 0, file: String = #file, line: UInt = #line, + _ body: () -> Void +) { + // Note: compare expected leak count with relative leak count after + // running `body`. + // This approach is more robust than comparing leak count with zero + // and resetting leak count to zero, which is stateful and causes issues. + let beforeLeakCount = _GlobalLeakCount.count + body() + let leakCount = _GlobalLeakCount.count - beforeLeakCount + expectEqual( + expectedLeakCount, leakCount, "Leaks detected: \(leakCount)", + file: file, line: line) +} + +public extension TestSuite { + /// Execute test function and check expected leak count. + func testWithLeakChecking( + _ name: String, + expectedLeakCount: Int = 0, + file: String = #file, line: UInt = #line, + _ testFunction: @escaping () -> Void + ) { + test(name, file: file, line: line) { + withLeakChecking(expectedLeakCount: expectedLeakCount, file: file, + line: line, testFunction) + } + } +} + +/// A type that tracks the number of live instances of a wrapped value type. +/// +/// `Tracked` is used to check for memory leaks in functions created via +/// automatic differentiation. +public struct Tracked { + fileprivate class Box { + fileprivate var value : T + init(_ value: T) { + self.value = value + _GlobalLeakCount.count += 1 + } + deinit { + _GlobalLeakCount.count -= 1 + } + } + private var handle: Box + + @differentiable(where T : Differentiable, T == T.TangentVector) + public init(_ value: T) { + self.handle = Box(value) + } + + @differentiable(where T : Differentiable, T == T.TangentVector) + public var value: T { + get { handle.value } + set { handle.value = newValue } + } +} + +extension Tracked : ExpressibleByFloatLiteral where T : ExpressibleByFloatLiteral { + public init(floatLiteral value: T.FloatLiteralType) { + self.handle = Box(T(floatLiteral: value)) + } +} + +extension Tracked : CustomStringConvertible { + public var description: String { return "Tracked(\(value))" } +} + +extension Tracked : ExpressibleByIntegerLiteral where T : ExpressibleByIntegerLiteral { + public init(integerLiteral value: T.IntegerLiteralType) { + self.handle = Box(T(integerLiteral: value)) + } +} + +extension Tracked : Comparable where T : Comparable { + public static func < (lhs: Tracked, rhs: Tracked) -> Bool { + return lhs.value < rhs.value + } + public static func <= (lhs: Tracked, rhs: Tracked) -> Bool { + return lhs.value <= rhs.value + } + public static func > (lhs: Tracked, rhs: Tracked) -> Bool { + return lhs.value > rhs.value + } + public static func >= (lhs: Tracked, rhs: Tracked) -> Bool { + return lhs.value >= rhs.value + } +} + +extension Tracked : AdditiveArithmetic where T : AdditiveArithmetic { + public static var zero: Tracked { return Tracked(T.zero) } + public static func + (lhs: Tracked, rhs: Tracked) -> Tracked { + return Tracked(lhs.value + rhs.value) + } + public static func - (lhs: Tracked, rhs: Tracked) -> Tracked { + return Tracked(lhs.value - rhs.value) + } +} + +extension Tracked : Equatable where T : Equatable { + public static func == (lhs: Tracked, rhs: Tracked) -> Bool { + return lhs.value == rhs.value + } +} + +extension Tracked : SignedNumeric & Numeric where T : SignedNumeric, T == T.Magnitude { + public typealias Magnitude = Tracked + + public init?(exactly source: U) where U : BinaryInteger { + if let t = T(exactly: source) { + self.init(t) + } + return nil + } + public var magnitude: Magnitude { return Magnitude(value.magnitude) } + + public static func * (lhs: Tracked, rhs: Tracked) -> Tracked { + return Tracked(lhs.value * rhs.value) + } + + public static func *= (lhs: inout Tracked, rhs: Tracked) { + lhs = lhs * rhs + } +} + +extension Tracked where T : FloatingPoint { + public static func / (lhs: Tracked, rhs: Tracked) -> Tracked { + return Tracked(lhs.value / rhs.value) + } + + public static func /= (lhs: inout Tracked, rhs: Tracked) { + lhs = lhs / rhs + } +} + +extension Tracked : Strideable where T : Strideable, T.Stride == T.Stride.Magnitude { + public typealias Stride = Tracked + + public func distance(to other: Tracked) -> Stride { + return Stride(value.distance(to: other.value)) + } + public func advanced(by n: Stride) -> Tracked { + return Tracked(value.advanced(by: n.value)) + } +} + +// For now, `T` must be restricted to trivial types (like `Float` or `Tensor`). +extension Tracked : Differentiable where T : Differentiable, T == T.TangentVector { + public typealias TangentVector = Tracked +} + +extension Tracked where T : Differentiable, T == T.TangentVector { + @usableFromInline + @derivative(of: init) + internal static func _vjpInit(_ value: T) + -> (value: Self, pullback: (Self.TangentVector) -> (T.TangentVector)) { + return (Tracked(value), { v in v.value }) + } + + @usableFromInline + @derivative(of: init) + internal static func _jvpInit(_ value: T) + -> (value: Self, differential: (T.TangentVector) -> (Self.TangentVector)) { + return (Tracked(value), { v in Tracked(v) }) + } + + @usableFromInline + @derivative(of: value) + internal func _vjpValue() -> (value: T, pullback: (T.TangentVector) -> Self.TangentVector) { + return (value, { v in Tracked(v) }) + } + + @usableFromInline + @derivative(of: value) + internal func _jvpValue() -> (value: T, differential: (Self.TangentVector) -> T.TangentVector) { + return (value, { v in v.value }) + } +} + +extension Tracked where T : Differentiable, T == T.TangentVector { + @usableFromInline + @derivative(of: +) + internal static func _vjpAdd(lhs: Self, rhs: Self) + -> (value: Self, pullback: (Self) -> (Self, Self)) { + return (lhs + rhs, { v in (v, v) }) + } + + @usableFromInline + @derivative(of: +) + internal static func _jvpAdd(lhs: Self, rhs: Self) + -> (value: Self, differential: (Self, Self) -> Self) { + return (lhs + rhs, { $0 + $1 }) + } + + @usableFromInline + @derivative(of: -) + internal static func _vjpSubtract(lhs: Self, rhs: Self) + -> (value: Self, pullback: (Self) -> (Self, Self)) { + return (lhs - rhs, { v in (v, .zero - v) }) + } + + @usableFromInline + @derivative(of: -) + internal static func _jvpSubtract(lhs: Self, rhs: Self) + -> (value: Self, differential: (Self, Self) -> Self) { + return (lhs - rhs, { $0 - $1 }) + } +} + +extension Tracked where T : Differentiable & SignedNumeric, T == T.Magnitude, + T == T.TangentVector { + @usableFromInline + @derivative(of: *) + internal static func _vjpMultiply(lhs: Self, rhs: Self) + -> (value: Self, pullback: (Self) -> (Self, Self)) { + return (lhs * rhs, { v in (v * rhs, v * lhs) }) + } + + @usableFromInline + @derivative(of: *) + internal static func _jvpMultiply(lhs: Self, rhs: Self) + -> (value: Self, differential: (Self, Self) -> (Self)) { + return (lhs * rhs, { (dx, dy) in dx * rhs + dy * lhs }) + } +} + +extension Tracked where T : Differentiable & FloatingPoint, T == T.TangentVector { + @usableFromInline + @derivative(of: /) + internal static func _vjpDivide(lhs: Self, rhs: Self) + -> (value: Self, pullback: (Self) -> (Self, Self)) { + return (lhs / rhs, { v in (v / rhs, -lhs / (rhs * rhs) * v) }) + } + + @usableFromInline + @derivative(of: /) + internal static func _jvpDivide(lhs: Self, rhs: Self) + -> (value: Self, differential: (Self, Self) -> (Self)) { + return (lhs / rhs, { (dx, dy) in dx / rhs - lhs / (rhs * rhs) * dy }) + } +} + +// Differential operators for `Tracked`. + +public func gradient( + at x: T, in f: @differentiable (T) -> Tracked +) -> T.TangentVector where R.TangentVector == R { + return pullback(at: x, in: f)(1) +} + +public func gradient( + at x: T, _ y: U, in f: @differentiable (T, U) -> Tracked +) -> (T.TangentVector, U.TangentVector) where R.TangentVector == R { + return pullback(at: x, y, in: f)(1) +} + +public func derivative( + at x: Tracked, in f: @differentiable (Tracked) -> R +) -> R.TangentVector where T.TangentVector == T { + return differential(at: x, in: f)(1) +} + +public func derivative( + at x: Tracked, _ y: Tracked, + in f: @differentiable (Tracked, Tracked) -> R +) -> R.TangentVector where T.TangentVector == T, U.TangentVector == U { + return differential(at: x, y, in: f)(1, 1) +} + +public func valueWithGradient( + at x: T, in f: @differentiable (T) -> Tracked +) -> (value: Tracked, gradient: T.TangentVector) { + let (y, pullback) = valueWithPullback(at: x, in: f) + return (y, pullback(1)) +} + +public func valueWithDerivative( + at x: Tracked, in f: @differentiable (Tracked) -> R +) -> (value: R, derivative: R.TangentVector) { + let (y, differential) = valueWithDifferential(at: x, in: f) + return (y, differential(1)) +} diff --git a/test/AutoDiff/validation-test/simple_math.swift b/test/AutoDiff/validation-test/simple_math.swift new file mode 100644 index 0000000000000..33495d407cb98 --- /dev/null +++ b/test/AutoDiff/validation-test/simple_math.swift @@ -0,0 +1,402 @@ +// RUN: %target-run-simple-swift +// NOTE(TF-813): verify that enabling forward-mode does not affect reverse-mode. +// RUN: %target-run-simple-swift(-Xfrontend -enable-experimental-forward-mode-differentiation) +// RUN: %target-swift-frontend -Xllvm -sil-print-after=differentiation %s -emit-sil -o /dev/null 2>&1 | %FileCheck %s +// REQUIRES: executable_test + +import StdlibUnittest +import DifferentiationUnittest + +var SimpleMathTests = TestSuite("SimpleMath") + +SimpleMathTests.test("Arithmetics") { + func foo1(x: Float, y: Float) -> Float { + return x * y + } + expectEqual((4, 3), gradient(at: 3, 4, in: foo1)) + func foo2(x: Float, y: Float) -> Float { + return -x * y + } + expectEqual((-4, -3), gradient(at: 3, 4, in: foo2)) + func foo3(x: Float, y: Float) -> Float { + return -x + y + } + expectEqual((-1, 1), gradient(at: 3, 4, in: foo3)) +} + +SimpleMathTests.test("Fanout") { + func foo1(x: Float) -> Float { + x - x + } + expectEqual(0, gradient(at: 100, in: foo1)) + func foo2(x: Float) -> Float { + x + x + } + expectEqual(2, gradient(at: 100, in: foo2)) + func foo3(x: Float, y: Float) -> Float { + x + x + x * y + } + expectEqual((4, 3), gradient(at: 3, 2, in: foo3)) +} + +SimpleMathTests.test("FunctionCall") { + func foo(_ x: Float, _ y: Float) -> Float { + return 3 * x + { $0 * 3 }(3) * y + } + expectEqual((3, 9), gradient(at: 3, 4, in: foo)) + expectEqual(3, gradient(at: 3) { x in foo(x, 4) }) +} + +SimpleMathTests.test("ResultSelection") { + func foo(_ x: Float, _ y: Float) -> (Float, Float) { + return (x + 1, y + 2) + } + expectEqual((1, 0), gradient(at: 3, 3, in: { x, y in foo(x, y).0 })) + expectEqual((0, 1), gradient(at: 3, 3, in: { x, y in foo(x, y).1 })) +} + +SimpleMathTests.test("CaptureLocal") { + let z: Float = 10 + func foo(_ x: Float) -> Float { + return z * x + } + expectEqual(10, gradient(at: 0, in: foo)) +} + +var globalVar: Float = 10 +SimpleMathTests.test("CaptureGlobal") { + func foo(x: Float) -> Float { + globalVar += 20 + return globalVar * x + } + expectEqual(30, gradient(at: 0, in: foo)) +} + +var foo_diffable: @differentiable (Float) -> (Float) + = differentiableFunction { x in (x * x, { v in 2 * x * v }) } +SimpleMathTests.test("GlobalDiffableFunc") { + expectEqual(2, gradient(at: 1, in: foo_diffable)) + expectEqual(2, gradient(at: 1, in: { x in foo_diffable(x) })) + expectEqual(1, gradient(at: 1, in: { (x: Float) -> Float in + foo_diffable = { x in x + 1 } + return foo_diffable(x) + })) + expectEqual(1, gradient(at: 1, in: foo_diffable)) +} + +SimpleMathTests.test("Mutation") { + func fourthPower(x: Float) -> Float { + var a = x + a = a * x + a = a * x + return a * x + } + expectEqual(4 * 27, gradient(at: 3, in: fourthPower)) +} + +SimpleMathTests.test("Tuple") { + // TF-945: Nested tuple projections. + func nested(_ x: Float) -> Float { + var tuple = (1, 1, ((x, 1), 1)) + return tuple.2.0.0 + } + expectEqual(1, gradient(at: 3, in: nested)) +} + +SimpleMathTests.test("TupleMutation") { + func foo(_ x: Float) -> Float { + var tuple = (x, x) + tuple.0 = tuple.0 * x + return x * tuple.0 + } + expectEqual(27, gradient(at: 3, in: foo)) + + func fifthPower(_ x: Float) -> Float { + var tuple = (x, x) + tuple.0 = tuple.0 * x + tuple.1 = tuple.0 * x + return tuple.0 * tuple.1 + } + expectEqual(405, gradient(at: 3, in: fifthPower)) + + func nested(_ x: Float) -> Float { + var tuple = ((x, x), x) + tuple.0.0 = tuple.0.0 * x + tuple.0.1 = tuple.0.0 * x + return tuple.0.0 * tuple.0.1 + } + expectEqual(405, gradient(at: 3, in: nested)) + + func generic(_ x: T) -> T { + var tuple = (x, x) + return tuple.0 + } + expectEqual(1, gradient(at: 3.0, in: generic)) + + // FIXME(TF-1033): Fix forward-mode ownership error for tuple with non-active + // initial values. + /* + func genericInitialNonactive( + _ x: T + ) -> T { + var tuple = (T.zero, T.zero) + tuple.0 = x + tuple.1 = x + return tuple.0 + } + expectEqual(1, gradient(at: 3.0, in: genericInitialNonactive)) + */ +} + +// Tests TF-321. +SimpleMathTests.test("TupleNonDifferentiableElements") { + // TF-964: Test tuple with non-tuple-typed adjoint value. + func tupleLet(_ x: Tracked) -> Tracked { + let tuple = (2 * x, 1) + return tuple.0 + } + expectEqual((8, 2), valueWithGradient(at: 4, in: tupleLet)) + + func tupleVar(_ x: Tracked) -> Tracked { + var tuple = (x, 1) + tuple.0 = x + tuple.1 = 1 + return tuple.0 + } + expectEqual((3, 1), valueWithGradient(at: 3, in: tupleVar)) + + func nested(_ x: Tracked) -> Tracked { + // Convoluted function computing `x * x`. + var tuple: (Int, (Int, Tracked), Tracked) = (1, (1, 0), 0) + tuple.0 = 1 + tuple.1.0 = 1 + tuple.1.1 = x + tuple.2 = x + return tuple.1.1 * tuple.2 + } + expectEqual((16, 8), valueWithGradient(at: 4, in: nested)) + + struct Wrapper { + @differentiable(where T : Differentiable) + func baz(_ x: T) -> T { + var tuple = (1, 1, x, 1) + tuple.0 = 1 + tuple.2 = x + tuple.3 = 1 + return tuple.2 + } + } + func wrapper(_ x: Tracked) -> Tracked { + let w = Wrapper>() + return w.baz(x) + } + expectEqual((3, 1), valueWithGradient(at: 3, in: wrapper)) +} + +// Tests TF-21. +SimpleMathTests.test("StructMemberwiseInitializer") { + struct Foo : AdditiveArithmetic, Differentiable { + var stored: Float + var computed: Float { + return stored * stored + } + } + + let 𝛁foo = pullback(at: Float(4), in: { input -> Foo in + let foo = Foo(stored: input) + let foo2 = foo + foo + return Foo(stored: foo2.stored) + })(Foo.TangentVector(stored: 1)) + expectEqual(2, 𝛁foo) + + let 𝛁computed = gradient(at: Float(4)) { input -> Float in + let foo = Foo(stored: input) + return foo.computed + } + expectEqual(8, 𝛁computed) + + let 𝛁product = gradient(at: Float(4)) { input -> Float in + let foo = Foo(stored: input) + return foo.computed * foo.stored + } + expectEqual(48, 𝛁product) + + struct Custom : AdditiveArithmetic, Differentiable { + var x: Float + + // Custom initializer with `@differentiable`. + @differentiable + init(x: Float) { + print(x) + self.x = x + } + } + + let 𝛁custom = pullback(at: Float(4), in: { input -> Custom in + let foo = Custom(x: input) + return foo + foo + })(Custom.TangentVector(x: 1)) + expectEqual(2, 𝛁custom) +} + +// Tests TF-319: struct with non-differentiable constant stored property. +SimpleMathTests.test("StructConstantStoredProperty") { + struct TF_319 : Differentiable { + var x: Float + @noDerivative let constant = Float(2) + + @differentiable + init(x: Float) { + self.x = x + } + + @differentiable(wrt: (self, input)) + func applied(to input: Float) -> Float { + return x * constant * input + } + } + func testStructInit(to input: Float) -> Float { + let model = TF_319(x: 10) + return model.applied(to: input) + } + expectEqual(TF_319.TangentVector(x: 6), + gradient(at: TF_319(x: 10), in: { $0.applied(to: 3) })) + expectEqual(20, gradient(at: 3, in: testStructInit)) +} + +SimpleMathTests.test("StructMutation") { + struct Point : AdditiveArithmetic, Differentiable { + var x: Float + var y: Float + var z: Float + } + + func double(_ input: Float) -> Point { + let point = Point(x: input, y: input, z: input) + return point + point + } + expectEqual(6, pullback(at: 4, in: double)(Point(x: 1, y: 1, z: 1))) + + func fifthPower(_ input: Float) -> Float { + var point = Point(x: input, y: input, z: input) + point.x = point.x * input + point.y = point.x * input + return point.x * point.y + } + expectEqual(405, gradient(at: 3, in: fifthPower)) + + func mix(_ input: Float) -> Float { + var tuple = (point: Point(x: input, y: input, z: input), float: input) + tuple.point.x = tuple.point.x * tuple.float + tuple.point.y = tuple.point.x * input + return tuple.point.x * tuple.point.y + } + expectEqual(405, gradient(at: 3, in: mix)) + + // Test TF-282. + struct Add : Differentiable { + var bias: Float + func applied(to input: Float) -> Float { + var tmp = input + tmp = tmp + bias + return tmp + } + } + let model = Add(bias: 1) + expectEqual(Add.TangentVector(bias: 1), + gradient(at: model) { m in m.applied(to: 1) }) +} + +SimpleMathTests.test("StructGeneric") { + struct Generic : AdditiveArithmetic, Differentiable { + var x: T + var y: T + var z: T + } + + let 𝛁generic = pullback(at: Float(3), in: { input -> Generic in + var generic = Generic(x: input, y: input, z: input) + return generic + })(Generic.TangentVector(x: 1, y: 1, z: 1)) + expectEqual(3, 𝛁generic) + + func fifthPower(_ input: Float) -> Float { + var generic = Generic(x: input, y: input, z: input) + generic.x = generic.x * input + generic.y = generic.x * input + return generic.x * generic.y + } + expectEqual(405, gradient(at: 3, in: fifthPower)) +} + +SimpleMathTests.test("StructWithNoDerivativeProperty") { + struct NoDerivativeProperty : Differentiable { + var x: Float + @noDerivative var y: Float + } + expectEqual( + NoDerivativeProperty.TangentVector(x: 1), + gradient(at: NoDerivativeProperty(x: 1, y: 1)) { s -> Float in + var tmp = s + tmp.y = tmp.x + return tmp.x + } + ) +} + +SimpleMathTests.test("SubsetIndices") { + func grad(_ lossFunction: @differentiable (Float, Float) -> Float) -> Float { + return gradient(at: 1) { x in lossFunction(x * x, 10.0) } + } + expectEqual(2, grad { x, y in x + y }) + + func gradWRTNonDiff(_ lossFunction: @differentiable (Float, @noDerivative Int) -> Float) -> Float { + return gradient(at: 2) { x in lossFunction(x * x, 10) } + } + expectEqual(4, gradWRTNonDiff { x, y in x + Float(y) }) +} + +SimpleMathTests.test("ForceUnwrapping") { + func forceUnwrap(_ t: T) + -> (T, Float) where T == T.TangentVector { + gradient(at: t, Float(1)) { (x, y) in + (x as! Float) * y + } + } + expectEqual((1, 2), forceUnwrap(Float(2))) +} + +// CHECK-LABEL: sil private [ossa] @AD__${{.*}}jumpTimesTwo{{.*}}pullback_src_0_wrt_0 : $@convention(thin) (Float, @owned _AD__$s4nullyycfU18_12jumpTimesTwoL_5modelSfAAyycfU18_14SmallTestModelL_V_tF_bb0__PB__src_0_wrt_0) -> SmallTestModel.TangentVector { +// CHECK: bb0([[DX:%.*]] : $Float, [[PB_STRUCT:%.*]] : {{.*}}): +// CHECK: ([[PB0:%.*]], [[PB1:%.*]]) = destructure_struct [[PB_STRUCT]] +// CHECK: [[ADJ_TUPLE:%.*]] = apply [[PB1]]([[DX]]) : $@callee_guaranteed (Float) -> (Float, Float) +// CHECK: ([[TMP0:%.*]], [[ADJ_CONCRETE:%.*]]) = destructure_tuple [[ADJ_TUPLE]] : $(Float, Float) +// CHECK: [[TMP1:%.*]] = apply [[PB0]]([[TMP0]]) : $@callee_guaranteed (Float) -> SmallTestModel.TangentVector +// CHECK: [[ADJ_STRUCT_FIELD:%.*]] = destructure_struct [[TMP1]] : $SmallTestModel.TangentVector +// CHECK: [[TMP_RES:%.*]] = alloc_stack $Float +// CHECK: [[TMP_ADJ_STRUCT_FIELD:%.*]] = alloc_stack $Float +// CHECK: [[TMP_ADJ_CONCRETE:%.*]] = alloc_stack $Float +// CHECK: store [[ADJ_STRUCT_FIELD]] to [trivial] [[TMP_ADJ_STRUCT_FIELD]] : $*Float +// CHECK: store [[ADJ_CONCRETE]] to [trivial] [[TMP_ADJ_CONCRETE]] : $*Float +// CHECK: [[PLUS_EQUAL:%.*]] = witness_method $Float, #AdditiveArithmetic."+" +// CHECK: %{{.*}} = apply [[PLUS_EQUAL]]([[TMP_RES]], [[TMP_ADJ_CONCRETE]], [[TMP_ADJ_STRUCT_FIELD]], {{.*}}) +// CHECK: [[RES:%.*]] = load [trivial] [[TMP_RES]] : $*Float +// CHECK: [[RES_STRUCT:%.*]] = struct $SmallTestModel.TangentVector ([[RES]] : $Float) +// CHECK: return [[RES_STRUCT]] : $SmallTestModel.TangentVector +// CHECK: } + +SimpleMathTests.test("Struct") { + // TF-943: Test adjoint value accumulation for aggregate lhs and concrete rhs. + struct SmallTestModel : Differentiable { + public var jump: Float = 3.0 + @differentiable public func callAsFunction() -> Float { return jump } + } + + func jumpTimesTwo(model: SmallTestModel) -> Float{ + return model() + model.jump + } + let grads = gradient(at: SmallTestModel(), in: jumpTimesTwo) + expectEqual(2.0, grads.jump) +} + +runAllTests() diff --git a/test/AutoDiff/validation-test/simple_model.swift b/test/AutoDiff/validation-test/simple_model.swift new file mode 100644 index 0000000000000..e929ad280aba5 --- /dev/null +++ b/test/AutoDiff/validation-test/simple_model.swift @@ -0,0 +1,103 @@ +// RUN: %target-run-simple-swift +// REQUIRES: executable_test + +import StdlibUnittest +import DifferentiationUnittest + +var SimpleModelTests = TestSuite("SimpleModel") + +struct DenseLayer : Equatable { + @differentiable + let w: Tracked + + @differentiable + let b: Tracked +} + +extension DenseLayer : Differentiable, AdditiveArithmetic { + typealias TangentVector = DenseLayer + typealias Scalar = Tracked + static var zero: DenseLayer { + return DenseLayer(w: 0, b: 0) + } + static func + (lhs: DenseLayer, rhs: DenseLayer) -> DenseLayer { + return DenseLayer(w: lhs.w + rhs.w, b: lhs.b + rhs.b) + } + static func - (lhs: DenseLayer, rhs: DenseLayer) -> DenseLayer { + return DenseLayer(w: lhs.w - rhs.w, b: lhs.b - rhs.b) + } + static func * (lhs: DenseLayer, rhs: DenseLayer) -> DenseLayer { + return DenseLayer(w: lhs.w * rhs.w, b: lhs.b * rhs.b) + } + static func * (lhs: Tracked, rhs: DenseLayer) -> DenseLayer { + return DenseLayer(w: lhs * rhs.w, b: lhs * rhs.b) + } +} + +extension DenseLayer { + func prediction(for input: Tracked) -> Tracked { + return input * w + b + } +} + +struct Model : Equatable { + @differentiable + let l1: DenseLayer + + @differentiable + let l2: DenseLayer + + @differentiable + let l3: DenseLayer +} + +extension Model : Differentiable, AdditiveArithmetic { + typealias TangentVector = Model + typealias Scalar = Tracked + static var zero: Model { + return Model(l1: DenseLayer.zero, l2: DenseLayer.zero, l3: DenseLayer.zero) + } + static func + (lhs: Model, rhs: Model) -> Model { + return Model(l1: lhs.l1 + rhs.l1, l2: lhs.l2 + rhs.l2, l3: lhs.l3 + rhs.l3) + } + static func - (lhs: Model, rhs: Model) -> Model { + return Model(l1: lhs.l1 - rhs.l1, l2: lhs.l2 - rhs.l2, l3: lhs.l3 - rhs.l3) + } + static func * (lhs: Model, rhs: Model) -> Model { + return Model(l1: lhs.l1 * rhs.l1, l2: lhs.l2 * rhs.l2, l3: lhs.l3 * rhs.l3) + } + static func * (lhs: Tracked, rhs: Model) -> Model { + return Model(l1: lhs * rhs.l1, l2: lhs * rhs.l2, l3: lhs * rhs.l3) + } +} + +extension Model { + func prediction(for input: Tracked) -> Tracked { + // This "model" is silly because it doesn't have nonlinearities. But it's + // simple and good enough for testing purposes. + let activation1 = l1.prediction(for: input) + let activation2 = l2.prediction(for: activation1) + return l3.prediction(for: activation2) + } + + func loss(of prediction: Tracked, from label: Tracked) -> Tracked { + return (prediction - label) * (prediction - label) + } +} + +SimpleModelTests.testWithLeakChecking("gradient") { + let layer = DenseLayer(w: 1.0, b: 0.0) + let model = Model(l1: layer, l2: layer, l3: layer) + let label: Tracked = 3 + let input: Tracked = 1 + let gradModel = gradient(at: model) { model -> Tracked in + let pred = model.prediction(for: input) + return model.loss(of: pred, from: label) + } + let expectedGrad = Model(l1: DenseLayer(w: -4, b: -4), + l2: DenseLayer(w: -4, b: -4), + l3: DenseLayer(w: -4, b: -4)) + expectEqual(expectedGrad, gradModel) +} + +runAllTests() diff --git a/validation-test/ParseableInterface/verify_all_overlays.py b/validation-test/ParseableInterface/verify_all_overlays.py index d98b64fd8bf6a..94b692e60baaa 100755 --- a/validation-test/ParseableInterface/verify_all_overlays.py +++ b/validation-test/ParseableInterface/verify_all_overlays.py @@ -49,7 +49,12 @@ else: continue - if module_name in ["_Differentiation", "Swift", "SwiftLang"]: + if module_name in [ + "_Differentiation", + "DifferentiationUnittest", + "Swift", + "SwiftLang", + ]: continue # swift -build-module-from-parseable-interface