Skip to content

[AutoDiff] Type-checking support for inout parameter differentiation. #29959

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

Merged
merged 4 commits into from
Feb 21, 2020

Conversation

dan-zheng
Copy link
Contributor

@dan-zheng dan-zheng commented Feb 20, 2020

Semantically, an inout parameter is both a parameter and a result.

@differentiable and @derivative attributes now support original functions
with one "semantic result": either a formal result or an inout parameter.

The differential/pullback type of a function with inout differentiability
parameters also has inout parameters. This is ideal for performance.

Differential typing rules:

  • Case 1: original function has no inout parameters.
    • Original: (T0, T1, ...) -> R
    • Differential: (T0.Tan, T1.Tan, ...) -> R.Tan
  • Case 2: original function has a non-wrt inout parameter.
    • Original: (T0, inout T1, ...) -> Void
    • Differential: (T0.Tan, ...) -> T1.Tan
  • Case 3: original function has a wrt inout parameter.
    • Original: (T0, inout T1, ...) -> Void
    • Differential: (T0.Tan, inout T1.Tan, ...) -> Void

Pullback typing rules:

  • Case 1: original function has no inout parameters.
    • Original: (T0, T1, ...) -> R
    • Pullback: R.Tan -> (T0.Tan, T1.Tan, ...)
  • Case 2: original function has a non-wrt inout parameter.
    • Original: (T0, inout T1, ...) -> Void
    • Pullback: (T1.Tan) -> (T0.Tan, ...)
  • Case 3: original function has a wrt inout parameter.
    • Original: (T0, inout T1, ...) -> Void
    • Pullback: (inout T1.Tan) -> (T0.Tan, ...)

Resolves TF-1164.


Examples:

extension Float {
  @derivative(of: *=)
  static func _vjpMultiplyAssign(_ lhs: inout Float, _ rhs: Float) -> (
    value: Void, pullback: (inout Float) -> Float
  ) {
    defer { lhs *= rhs }
    return ((), { [lhs = lhs] v in
      let drhs = lhs * v
      v *= rhs
      return drhs
    })
  }
}

extension Array where Element: Differentiable {
  @derivative(of: append)
  mutating func _vjpAppend(_ element: Element) -> (
    value: Void, pullback: (inout TangentVector) -> Element.TangentVector
  ) {
    let appendedElementIndex = count
    defer { append(element) }
    return ((), { dself in dself.base[appendedElementIndex] })
  }
}

This patch includes all inout parameter differentiation changes currently
relevant to master branch, e.g. SIL derivative type calculation changes.

Remaining changes involves code that hasn't yet been upstreamed. Those changes
will be committed to tensorflow branch and eventually upstreamed.

End-to-end inout parameter differentiation is tested in #29956.

@dan-zheng dan-zheng requested review from rxwei and marcrasi February 20, 2020 16:25
@dan-zheng
Copy link
Contributor Author

Let's check that tests pass.
@swift-ci Please smoke test

@@ -1158,6 +1146,42 @@ final class FinalClass: Differentiable {
}
}

// Test `inout` parameters.

@differentiable(wrt: y)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note: I intentionally didn't add tests for @differentiable(jvp:vjp), since that's actively being removed (TF-1162).

Semantically, an `inout` parameter is both a parameter and a result.

`@differentiable` and `@derivative` attributes now support original functions
with one "semantic result": either a formal result type or an `inout` parameter.

The differential/pullback type of a function with `inout` differentiability
parameters also has `inout` parameters. This is ideal for performance.

Differential typing rules:
- Case 1: original function has no `inout` parameters.
  - Original:     `(T0, T1, ...) -> R`
  - Differential: `(T0.Tan, T1.Tan, ...) -> R.Tan`
- Case 2: original function has a non-wrt `inout` parameter.
  - Original:     `(T0, inout T1, ...) -> Void`
  - Differential: `(T0.Tan, ...) -> T1.Tan`
- Case 3: original function has a wrt `inout` parameter.
  - Original:     `(T0, inout T1, ...) -> R`
  - Differential: `(T0.Tan, inout T1.Tan, ...) -> Void`

Pullback typing rules:
- Case 1: original function has no `inout` parameters.
  - Original: `(T0, T1, ...) -> R`
  - Pullback: `R.Tan -> (T0.Tan, T1.Tan, ...)`
- Case 2: original function has a non-wrt `inout` parameter.
  - Original: `(T0, inout T1, ...) -> Void`
  - Pullback: `(T1.Tan) -> (T0.Tan, ...)`
- Case 3: original function has a wrt `inout` parameter.
  - Original: `(T0, inout T1, ...) -> R`
  - Pullback: `(inout T1.Tan) -> (T0.Tan, ...)`

Resolves TF-1164.
@dan-zheng dan-zheng force-pushed the autodiff-inout-parameters branch from be7f093 to 44cc330 Compare February 20, 2020 17:32
@dan-zheng
Copy link
Contributor Author

@swift-ci Please smoke test

- `SemanticFunctionResultType` -> `AutoDiffSemanticFunctionResultType`
- `getAutoDiffDerivativeFunctionLinearMapResultType` ->
  `getAutoDiffDerivativeFunctionLinearMapType`
@dan-zheng dan-zheng force-pushed the autodiff-inout-parameters branch from bb68111 to 165051e Compare February 20, 2020 23:42
Rename `getOriginalFunctionSemanticResultType` to `getFunctionSemanticResultType`.

It is no longer strictly related to "original" functions: scrub all mentions of
"original".
@dan-zheng dan-zheng requested a review from rxwei February 21, 2020 06:34
@rxwei
Copy link
Contributor

rxwei commented Feb 21, 2020

Case 3: original function has a wrt inout parameter.

  • Original: (T0, inout T1, ...) -> R
  • Differential: (T0.Tan, inout T1.Tan, ...) -> Void
    Case 3: original function has a wrt inout parameter.
  • Original: (T0, inout T1, ...) -> R
  • Pullback: (inout T1.Tan) -> (T0.Tan, ...)

I don't think these are correct. R should be a Void because those the wrt inout parameter already behaves as a result. There cannot be a second result.

@rxwei
Copy link
Contributor

rxwei commented Feb 21, 2020

static func _vjpMultiplyAssign(_ lhs: inout Float, _ rhs: ${Self}) -> (

There are multiple occurrences of ${Self} in your code example in the PR description.

@dan-zheng
Copy link
Contributor Author

@swift-ci Please smoke test

@dan-zheng dan-zheng merged commit 697c722 into swiftlang:master Feb 21, 2020
@dan-zheng dan-zheng deleted the autodiff-inout-parameters branch February 21, 2020 17:48
compnerd pushed a commit that referenced this pull request Feb 22, 2020
Merge #29959:
type-checking support for `inout` parameter differentiation.

Intentionally does not fix failing `inout` parameter differentiation tests.
Those will be fixed in a separate PR to faciliate code review.

```
Failing Tests (3):
    Swift(linux-x86_64) :: AutoDiff/downstream/forward_mode_diagnostics.swift
    Swift(linux-x86_64) :: AutoDiff/downstream/activity_analysis.swift
    Swift(linux-x86_64) :: AutoDiff/downstream/differentiation_transform_diagnostics.swift
```

Lesson learned: next time when doing AutoDiff changes that touch both
upstream and downstream code, create PRs to both `tensorflow` and `master`
branch, and merge the `tensorflow` branch PR first. This minimizes conflicts
during the `tensorflow -> master` merge process.
dan-zheng added a commit that referenced this pull request Feb 23, 2020
Cherry-pick #29959:
AST and SIL typing rules for `inout` parameter differentiation.

---

Add reverse-mode differentiation support for `apply` with `inout` arguments.

Notable pullback generation changes:
- If the pullback seed argument is `inout`, assign it (rather than a copy)
  directly as the adjoint buffer of the original result. This is important so
  the value is updated in-place.
- In `visitApplyInst`: skip adjoint accumulation for `inout` arguments.
  Adjoint accumulation for `inout` arguments occurs when callee pullbacks are
  applied, so no extra accumulation is necessary.

Add derivatives for functions with `inout` parameters in the stdlib for testing:
- `FloatingPoint` operations: `+=`, `-=`, `*=`, `/=`
- `Array.append`

Resolves TF-1165.

Todos:
- Add more tests, e.g. SILGen tests for `inout` derivative typing rules.
- Evaluate performance of `inout` derivatives vs functional derivatives + mutation.
- TF-1166: enable `@differentiable` attribute on `set` accessors.
- TF-1173: add forward-mode differentiation support for `apply` with `inout`
  parameters.

Exposes TF-1175: incorrect activity for class arguments.
Exposes TF-1176: incorrect activity for class `modify` accessors.
Add negative tests.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants