Repository navigation
feat(compile): a mixed-precision layer's dtype is a checked preference (#1495) - #1497
Merged
Merged
Conversation
#1495) A layer or stage declared with a dtype reached a compiled graph as plain convert nodes with narrow-typed ops between them; nothing recorded what the user declared. DtypeForwardPropagationPass(targetFloatDtype = "BF16") turns an FP16 stage into a BF16 one, and no pass objected. A tracing context now follows the module call stack through its forward hooks and marks every op recorded inside a PrecisionBoundary with DTypePolicy.Prefer(innerType) and the boundary's name. The marks are node metadata, not operation parameters. The boundary's own two casts are not marked: they run in the boundary itself and have a different dtype on one side by design. Graphs traced without boundaries are unchanged. DTypeConstraintResolutionPass checks a boundary's preference against the node's floating-point inputs only (an index input is not a violation) and names the boundary in the diagnostic. Prefer, not Require: retyping a model for a single-dtype target stays allowed, but is no longer silent. Fix: the pass marked a policy-carrying node by removing and re-adding it, which dropped every edge connected to that node. It now rebuilds the graph with the edges intact and reports no change on an already resolved graph, so a multi-iteration pipeline converges. PrecisionBoundary.innerType / outerType are public. The tracing context's hooks wrap the caller's hooks and are never null. SKEEP-008 Open Question 4 is resolved. Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
|
📖 Documentation Preview The documentation has been built successfully for this PR. Generated Files:
Artifacts:
This comment will be updated automatically when the PR is updated. |
Merged
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #1495. Resolves Open Question 4 of SKEEP-008, the last open one.
Problem
dense(64, dtype = BF16)/stage("head", dtype = FP16) { … }reached a compiled graph as plainconvertnodes with narrow-typed ops between them. Nothing recorded what the user declared, so a pass that retypes the graph could change it unnoticed. The issue's first research task confirmed this:DtypeForwardPropagationPass(targetFloatDtype = "BF16")turns an FP16 stage into a BF16 one and no pass objected.Decision
Of the three options in the issue: metadata on the region, limited to precision boundaries (not a general module scope on every op), with
Preferrather thanRequire. No callback or strategy parameter.Change
skainet-compile-dag):DefaultGraphExecutionContextfollows the module call stack through its forward hooks (PrecisionBoundaryScope) and marks every op recorded inside aPrecisionBoundarywithDTypePolicy.Prefer(innerType)and the boundary's name (PRECISION_BOUNDARY_METADATA_KEY). The boundary's own two casts are not marked.TraceToGraphBuilderputs both in node metadata, not in operation parameters.skainet-compile-opt):DTypeConstraintResolutionPasschecks a boundary's preference against the node's floating-point inputs only and names the boundary:Node '…' (matmul) in precision boundary 'head' prefers Float16 but input 0 has dtype 'BF16' — using the existing dtype.PrecisionBoundary.innerType/outerTypeare public (skainet-lang-core).Fix found on the way
DTypeConstraintResolutionPassmarked a policy-carrying node withremoveNode+addNode, andremoveNodedrops every edge connected to the node. Anydag { … dtypePolicy(…) }graph left the pass with those nodes unwired; with this PR's tagging it would have hit every traced mixed-precision network. The pass now rebuilds the graph with the edges intact, and reportschanged = falseon an already resolved graph so a multi-iteration pipeline converges.Review notes
DefaultGraphExecutionContext.hooksnow wraps the caller's hooks and is never null; the constructor parameter is unchanged.withSchedulepasses the caller's hooks on, not the wrapper..apifiles regenerated for lang-core, compile-dag and compile-opt.Tests
MixedPrecisionExportTest(+5): ops inside each boundary carry the right policy and the casts, inputs and weights do not; a network without boundaries carries none; the constraint pass is silent on an untouched network and reports the retyped FP16 stage (and not the BF16 dense); cast folding keeps the policies; constraint resolution + cast folding leave the traced graph connected and exportable.DTypeConstraintResolutionPassTest(+3): resolved nodes keep their edges; a second run changes nothing; a boundary policy applies to float inputs only.Test plan
./gradlew apiCheck checkUnmarkedStubsjvmTestforskainet-compile-opt,skainet-compile-hlo,skainet-compile-dag(after rebase ondevelop)jvmTestforskainet-lang-core,skainet-lang-models,skainet-backend-cpuand linuxX64 compile of compile-dag / compile-opt (before the rebase)🤖 Generated with Claude Code