Skip to content

Fix FP64 promotion in PSGD running_lower_bound - #95

Merged
ClashLuke merged 6 commits into
HomebrewML:mainfrom
dlyr3:PSGD_optimizer_fp_64
Jul 11, 2026
Merged

Fix FP64 promotion in PSGD running_lower_bound#95
ClashLuke merged 6 commits into
HomebrewML:mainfrom
dlyr3:PSGD_optimizer_fp_64

Conversation

@dlyr3

@dlyr3 dlyr3 commented Jul 11, 2026

Copy link
Copy Markdown
Contributor

Problem

running_lower_bound was initialized as float64.
This propagated through _update_lb() into preconditioner matmuls (R @ q_, R @ RQ), causing CUTLASS to dispatch d884gemm (FP64 GEMM kernels).
On consumer GPUs (1:64 FP64:FP32 throughput) this consumed ~80% of GPU time.

Fix

Changed running_lower_bound init dtype from float64 to float32 (chainable.py lines 1041/1063/1096).
Added defensive .float() cast in _update_lb().

Results (RTX 3090, MLP 512x512 bf16, PSGDPRO defaults)

  • Wall time/step: 18.33ms -> 8.31ms (2.2x)
  • d884gemm kernels: 16.5% of GPU time -> 0%
  • Convergence: identical (see plot)

Before:
image

After:
image

Convergence:
image

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: e2ae8d736c

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread heavyball/utils.py Outdated
ell = promote(ell)
ell = ell.maximum(promote(lb_state) + (ell - promote(lb_state)) * (1 - beta))
copy_stochastic_(lb_state, ell)
ell = ell.float()

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve fp64 denominators for fp64 PSGD

When PSGD is configured with q_dtype="float64"/double gradients, this unconditional cast forces the lower-bound denominator back to fp32 right before it is used in update / ell. For double-precision runs whose preconditioner eigenvalue scale is outside fp32 range, ell.float() can underflow or overflow to 0/inf, producing infinite or zero preconditioner updates even though the Q factors and covariance were computed in fp64. Cast to the preconditioner/update dtype, or only force fp32 for the default non-fp64 Q/legacy-state path.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

@dlyr3 what's the most elegant solution here?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Casting is in the function rather than in the callable to avoid redundancy and potential oversight.

Also i didn't pass q_ dtype to not mix responsabilities and also because ell was promoted on its own, without regards to q_

@ClashLuke

Copy link
Copy Markdown
Member

that's a huge finding, thank you!

cc @opooladz: PSGD speedup incoming

Comment thread heavyball/utils.py Outdated
ell = promote(ell)
ell = ell.maximum(promote(lb_state) + (ell - promote(lb_state)) * (1 - beta))
copy_stochastic_(lb_state, ell)
ell = ell.float()

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

@dlyr3 what's the most elegant solution here?

Comment thread benchmarks/bench_psgd_fp64.py Outdated
@@ -0,0 +1,103 @@
"""Reproducer for FP64 promotion in PSGD preconditioner lower bound.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

we generally don't need issue reproduction scripts in the repository, but thank you for sharing it!

Comment thread benchmarks/bench_psgd_prod.py Outdated
import heavyball

def make_model(dim, device):
return nn.Sequential(

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

unrelated to heavyball, but bias is true by default and .to(device) and .to(dtype) are usually done outside of the construction

this specific file should not be in upstream either

Comment thread heavyball/chainable.py Outdated
)
state["Q"] = utils.triu_to_line(Q) if group["store_triu_as_line"] else Q
state["running_lower_bound"] = [torch.zeros((grad.shape[0],), device=q.device, dtype=torch.float64) for q in Q]
state["running_lower_bound"] = [torch.zeros((grad.shape[0],), device=q.device, dtype=torch.float32) for q in Q]

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

rlb was set to fp64 so that we have higher precision accumulation. it's a scalar, so it's ~free!

@@ -0,0 +1,84 @@
"""Test that PSGD preconditioner lower bound uses fp32, not fp64.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The scalar lower bound arithmethic should be fp64, only matmul and matrix-level elementwise computation should be standard precision

@dlyr3
dlyr3 marked this pull request as draft July 11, 2026 16:57
@dlyr3

dlyr3 commented Jul 11, 2026

Copy link
Copy Markdown
Contributor Author

thanks for your review ! please let me know if anything more could be improved

The test was ran and successful on my end

@dlyr3
dlyr3 marked this pull request as ready for review July 11, 2026 17:07
@ClashLuke

Copy link
Copy Markdown
Member

lgtm!

@ClashLuke
ClashLuke merged commit e578d5c into HomebrewML:main Jul 11, 2026
1 of 3 checks passed
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