Skip to content

Design doc for #1882: Issue #1882 Design: Statically Checked Autograd - #1884

Open
johnynek wants to merge 3 commits into
mainfrom
agent/design/1882-autograd
Open

Design doc for #1882: Issue #1882 Design: Statically Checked Autograd#1884
johnynek wants to merge 3 commits into
mainfrom
agent/design/1882-autograd

Conversation

@johnynek

@johnynek johnynek commented Mar 1, 2026

Copy link
Copy Markdown
Owner

Design doc.

Refs #1882

@johnynek johnynek mentioned this pull request Mar 1, 2026
@codecov

codecov Bot commented Mar 1, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 85.03%. Comparing base (0f3bea7) to head (6c15fc4).

⚠️ Current head 6c15fc4 differs from pull request most recent head 09078f9

Please upload reports for the commit 09078f9 to get more accurate results.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1884      +/-   ##
==========================================
  Coverage   85.03%   85.03%              
==========================================
  Files         190      176      -14     
  Lines       46891    34485   -12406     
  Branches    11805     8636    -3169     
==========================================
- Hits        39876    29326   -10550     
+ Misses       7015     5159    -1856     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.


Add compiler rule:

`Diff[a] <:< (a -> Float64)`

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

explain how we would do this. This seems to be a new kind of judgement in Infer. It seems like we would have to do a new kind of subsumption check where we add a runtime call during on the subsumption judgement.

For instance, if are judging Diff[b] <:< (a -> Float64) we would check if a <:< b and then apply run_Diff to the Diff[b] value to get the a -> Float64. Or something like this?

Please be more explicit with how Infer needs to change.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Updated the design doc with an explicit Infer plan in section Subsumption/coercion changes in Infer (explicit). It now specifies a new subsCheckRho2 case for Diff[b] <:< (a -> Float64), reducing through runType = b -> Float64, reusing existing function subsumption, and composing coercion by inserting run_Diff at runtime (coerce_fn(run_Diff(d))). It also states the contravariant consequence a <:< b through existing function rules.

### 3. Intrinsic typing rules

1. `diff(e)` requires `e` to typecheck as `a -> Float64`.
2. `diff(e)` runs a differentiability checker on typed `e`.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

explain in more detail how this differentiability checker looks. Now, we only compile one package at a time, and the only things we read from imports are the exported names and exported types. How does this compose? Can you only call other Diff[a] inside and some intrinsics? I guess that's right, but explain how.

Can we handle some branching? What kind? Be explicit.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Expanded Differentiability checker with a new subsection on composition under Bosatsu’s package model (imports only expose exported names/types). The doc now explicitly limits cross-package calls inside diff bodies to: local inspectable functions, known derivative-registry globals, or Diff-typed imported values. It also adds a concrete branching policy section: non-active conditions are allowed (with examples), active-input-dependent branch conditions are rejected in phase 1.

1. `Float64`
2. Tuples recursively composed of `Float64`

`Array[Float64]` is explicitly deferred in this issue (see rollout/follow-up). Calls that attempt `diff` on unsupported domains fail with dedicated autograd type errors.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

add more explanation why Array[Float64] is difficult. It's one of the most obvious use cases.

Also, what about data types that only have nested tuples inside:

struct Pair(p: (Float64, Float64))

could we handle recursions on these easily?

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Added a dedicated Why Array[Float64] is deferred section with concrete reasons (runtime shape handling, scatter-add accumulation, optimizer/lowering implications, and dynamic guards without shape types). Also added Product structs and recursive ADTs: non-recursive product structs like struct Pair(p: (Float64, Float64)) are called out as much easier (tuple-isomorphic tangent structure), while recursive ADTs remain out-of-scope in phase 1 due current recursion restrictions in diff bodies.

run_Diff,
)

struct Diff[a](run: a -> Float64, grad: a -> a)

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

I'm confused about how the type for grad is a -> a. Can you explain that in more detail and give examples with Float64 and tuples of Float64?

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Added a new Why grad has type a -> a subsection with explicit examples for Float64, (Float64, Float64), and nested tuples. The section now explains that gradient output mirrors the input product structure and shows representative signatures and sample partial-derivative interpretation.

```bosatsu
from Bosatsu/Num/Autograd import diff, grad

square = diff(x -> x * x)

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

what about cases where we want to have variables that aren't part of the differentiation? For instance we have a Bool and if true we have one function and if false another, but both of those functions are differentiable. This is a common case. How is this handled?

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Added explicit handling for non-differentiated control variables in Branching policy (explicit). The doc now distinguishes active vs non-active values and includes examples where a Bool controls which differentiable branch to use (choose/choose2) while still allowing differentiation with respect to float inputs. It also states that branch conditions depending on active differentiable inputs are rejected in phase 1.

@johnynek

johnynek commented Mar 2, 2026

Copy link
Copy Markdown
Owner Author

Addressed all five review comments by directly revising docs/design/1882-autograd.md (including cleaning duplicated front-matter/header content and expanding the requested technical details). Required pre-push check passed: scripts/test_basic.sh -> Passed: Total 61, Failed 0, Errors 0.

@johnynek

Copy link
Copy Markdown
Owner Author

origin/main has been merged into the PR branch, and the required pre-push check passed locally: scripts/test_basic.sh -> Passed: Total 67, Failed 0, Errors 0, Passed 67. The first cold run hit a timeout in ClangGenTest.test_workspace/Ackermann.bosatsu, but the isolated rerun passed and the subsequent full warm rerun passed cleanly.

@johnynek

Copy link
Copy Markdown
Owner Author

MergeXO feedback automation is blocked because the agent returned commit_message but no new staged changes or local commits were detected.

Action: request concrete file edits (or explicit git_ops), then reset blocked feedback state.

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.

1 participant