Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[SR-14053] Incorrect derivative after leaving inner function scope #56444

Closed
porterchild opened this issue Jan 15, 2021 · 2 comments
Closed

[SR-14053] Incorrect derivative after leaving inner function scope #56444

porterchild opened this issue Jan 15, 2021 · 2 comments
Assignees
Labels
AutoDiff bug A deviation from expected or documented behavior. Also: expected but undesirable behavior. compiler The Swift compiler in itself

Comments

@porterchild
Copy link

Previous ID SR-14053
Radar None
Original Reporter @porterchild
Type Bug
Status Resolved
Resolution Done
Additional Detail from JIRA
Votes 0
Component/s Compiler
Labels Bug, AutoDiff
Assignee @BradLarson
Priority Medium

md5: fe6a21d4d320144e6bcc478f511d7199

Issue Description:

The derivative below with respect to Bar.a is, correctly, '0' on the backwards pass while still inside the apply() function. Something happens between there and propagating out to the enclosing function, where the derivative incorrectly becomes '1'.

As a possible hint, making 'HoldsKeyPaths' only hold a single keyPath instead of an array makes the derivative propagate correctly.

This bug seems to be something of an 'inverse' to https://bugs.swift.org/browse/SR-14218, in which the derivative incorrectly goes from 1 to 0. Here it goes from 0 to 1.

import Foundation
import _Differentiation

//------------------------------------------------------------------------------------------
// Enable differentiable keyPath writes

//called 'writeSlowTo' because inout object wasn't working
@differentiable(where Object: Differentiable, Object == Object.TangentVector, Member: Differentiable, Member == Member.TangentVector)
public func writeSlowTo<Object, Member>(_ object: Object, at member: WritableKeyPath<Object, Member>, with value: Member) -> Object {
    var object = object
    object[keyPath: member] = value
    return object
}

@derivative(of: writeSlowTo)
public func vjpWriteSlowTo<Object, Member>(_ object: Object, at member: WritableKeyPath<Object, Member>, with value: Member) -> (value: Object, pullback: (Object.TangentVector) -> (Object.TangentVector, Member.TangentVector))
where Object: Differentiable, Object == Object.TangentVector, Member: Differentiable, Member == Member.TangentVector
{
    func pullback(_ dself: Object.TangentVector) -> (Object.TangentVector, Member.TangentVector) {
        var dself = dself
        let dWriteValue = dself[keyPath: member]
        dself[keyPath: member] = Member.zero
        return (dself, dWriteValue)
    }
    var object = object
    object = writeSlowTo(object, at: member, with: value)
    return (object, pullback)
}


//------------------------------------------------------------------------------------------
// Usage code
public struct Bar: Differentiable & AdditiveArithmetic {
    public var a: Double = 7
}

struct HoldsKeyPaths<Root, Value> {
    var keyPaths: [WritableKeyPath<Root, Value>] = []
        
    @differentiable(where Root: Differentiable, Root == Root.TangentVector, Value: Differentiable, Value == Value.TangentVector)
    public func apply(to root: inout Root, using value: Value) {
        for kp in self.keyPaths {
            //---
            // force cast to print gradient
            var rootbar = root as! Bar
            rootbar = rootbar.withDerivative{ print("correct derivative", $0) }
            root = rootbar as! Root
            //---
            root = writeSlowTo(root, at: kp, with: value)
        }
    }
}


let bar = Bar()
let keyPaths = [\Bar.a]
let keyPathHolder = HoldsKeyPaths<Bar, Double>(keyPaths: keyPaths)

func writeToBar(bar: Bar, newValue: Double) -> Double {
    var bar = bar
    bar = bar.withDerivative { print("actual derivative", $0) }
    keyPathHolder.apply(to: &bar, using: newValue)
    return bar.a
}

let newValue: Double = 7
let valAndGrad = valueWithGradient(at: bar, newValue, in: writeToBar)

if valAndGrad.gradient.0.a != 0 {
    print("gradient is incorrect")
}
 
//prints: 
//correct derivative Bar(a: 0.0)
//actual derivative Bar(a: 1.0)
//gradient is incorrect
@porterchild
Copy link
Author

Alternatively, (and maybe more insightfully), here is a version where HoldsKeyPaths only has one keypath, and adding a useless loop can toggle the bug:

(Thanks BradLarson (JIRA User))

import Foundation
import _Differentiation


//------------------------------------------------------------------------------------------
// Enable differentiable keyPath writes


//called 'writeSlowTo' because inout object wasn't working
@differentiable(where Object: Differentiable, Object == Object.TangentVector, Member: Differentiable, Member == Member.TangentVector)
public func writeSlowTo<Object, Member>(_ object: Object, at member: WritableKeyPath<Object, Member>, with value: Member) -> Object {
    var object = object
    object[keyPath: member] = value
    return object
}


@derivative(of: writeSlowTo)
public func vjpWriteSlowTo<Object, Member>(_ object: Object, at member: WritableKeyPath<Object, Member>, with value: Member) -> (value: Object, pullback: (Object.TangentVector) -> (Object.TangentVector, Member.TangentVector))
where Object: Differentiable, Object == Object.TangentVector, Member: Differentiable, Member == Member.TangentVector
{
    func pullback(_ dself: Object.TangentVector) -> (Object.TangentVector, Member.TangentVector) {
        var dself = dself
        let dWriteValue = dself[keyPath: member]
        dself[keyPath: member] = Member.zero
        return (dself, dWriteValue)
    }
    var object = object
    object = writeSlowTo(object, at: member, with: value)
    return (object, pullback)
}




//------------------------------------------------------------------------------------------
// Usage code
public struct Bar: Differentiable & AdditiveArithmetic {
    public var a: Double = 7
}


struct HoldsKeyPaths<Root, Value> {
    var keyPath: WritableKeyPath<Root, Value>


    @differentiable(where Root: Differentiable, Root == Root.TangentVector, Value: Differentiable, Value == Value.TangentVector)
    public func apply(to root: inout Root, using value: Value) {
        //---
        // force cast to print gradient
        var rootbar = root as! Bar
        rootbar = rootbar.withDerivative{ print("correct derivative", $0) }
        root = rootbar as! Root
        //---
        root = writeSlowTo(root, at: self.keyPath, with: value)
        for _ in 0..<1{} //useless loop, get rid of it and gradient is accurate
    }
}




let bar = Bar()
let keyPath = \Bar.a
let keyPathHolder = HoldsKeyPaths<Bar, Double>(keyPath: keyPath)


func writeToBar(bar: Bar, newValue: Double) -> Double {
    var bar = bar
    bar = bar.withDerivative { print("actual derivative", $0) }
    keyPathHolder.apply(to: &bar, using: newValue)
    return bar.a
}


let newValue: Double = 7
let valAndGrad = valueWithGradient(at: bar, newValue, in: writeToBar)


if valAndGrad.gradient.0.a != 0 {
    print("gradient is incorrect")
}
 
//prints:
//correct derivative Bar(a: 0.0)
//actual derivative Bar(a: 1.0)
//gradient is incorrect

@BradLarson
Copy link
Collaborator

Should be resolved by PR #37861.

@swift-ci swift-ci transferred this issue from apple/swift-issues Apr 25, 2022
This issue was closed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
AutoDiff bug A deviation from expected or documented behavior. Also: expected but undesirable behavior. compiler The Swift compiler in itself
Projects
None yet
Development

No branches or pull requests

2 participants