Skip to content

Don't create CUDA atomics for basic indices - #1441

Merged
vgvassilev merged 1 commit into
vgvassilev:masterfrom
ovdiiuv:atomics-ana
Aug 4, 2025
Merged

Don't create CUDA atomics for basic indices#1441
vgvassilev merged 1 commit into
vgvassilev:masterfrom
ovdiiuv:atomics-ana

Conversation

@ovdiiuv

@ovdiiuv ovdiiuv commented Jul 9, 2025

Copy link
Copy Markdown
Collaborator

Before this PR, we unconditionally produced atomic operations for writes to global gpu memory. For example, the gradient of the kernel below:

__global__ void indices_perm(int *out, int *in) {
   int index = threadIdx.x + blockIdx.x * blockDim.x;
   out[index] += in[index];
 }

used to have an atomicAdd in the reverse pass corresponding to the out[index] += in[index] statement. The way index is defined makes it injective, meaning no two threads in any two blocks would share the same index. In cases like this no data races occur, so we can avoid using atomic operations.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

clang-tidy made some suggestions

There were too many comments to post at once. Showing the first 10 out of 11. Check the log or trigger a new build to see more.

Comment thread lib/Differentiator/ReverseModeVisitor.cpp Outdated
Comment thread lib/Differentiator/ReverseModeVisitor.cpp Outdated
Comment thread lib/Differentiator/ReverseModeVisitor.cpp Outdated
Comment thread lib/Differentiator/ReverseModeVisitor.cpp Outdated
Comment thread lib/Differentiator/ReverseModeVisitor.cpp Outdated
Comment thread lib/Differentiator/ReverseModeVisitor.cpp Outdated
Comment thread lib/Differentiator/ReverseModeVisitor.cpp Outdated
Comment thread lib/Differentiator/ReverseModeVisitor.cpp
Comment thread lib/Differentiator/ReverseModeVisitor.cpp
Comment thread lib/Differentiator/ReverseModeVisitor.cpp Outdated
@codecov

codecov Bot commented Jul 9, 2025

Copy link
Copy Markdown

Codecov Report

All modified and coverable lines are covered by tests ✅

Project coverage is 94.78%. Comparing base (2028122) to head (72a6131).

Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##           master    #1441      +/-   ##
==========================================
+ Coverage   94.71%   94.78%   +0.07%     
==========================================
  Files          56       56              
  Lines        9762     9874     +112     
==========================================
+ Hits         9246     9359     +113     
+ Misses        516      515       -1     
Files with missing lines Coverage Δ
lib/Differentiator/ReverseModeVisitor.cpp 96.01% <100.00%> (+0.21%) ⬆️
Files with missing lines Coverage Δ
lib/Differentiator/ReverseModeVisitor.cpp 96.01% <100.00%> (+0.21%) ⬆️
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Comment thread lib/Differentiator/ReverseModeVisitor.cpp Outdated

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

clang-tidy made some suggestions

There were too many comments to post at once. Showing the first 10 out of 22. Check the log or trigger a new build to see more.

Comment thread lib/Differentiator/ReverseModeVisitor.cpp Outdated
Comment thread lib/Differentiator/ReverseModeVisitor.cpp Outdated
Comment thread lib/Differentiator/ReverseModeVisitor.cpp Outdated
Comment thread lib/Differentiator/ReverseModeVisitor.cpp Outdated
Comment thread lib/Differentiator/ReverseModeVisitor.cpp Outdated
Comment thread lib/Differentiator/ReverseModeVisitor.cpp Outdated
Comment thread lib/Differentiator/ReverseModeVisitor.cpp Outdated
Comment thread lib/Differentiator/ReverseModeVisitor.cpp Outdated
Comment thread lib/Differentiator/ReverseModeVisitor.cpp Outdated
Comment thread lib/Differentiator/ReverseModeVisitor.cpp Outdated
@ovdiiuv
ovdiiuv force-pushed the atomics-ana branch 2 times, most recently from 3de551c to e50e165 Compare July 10, 2025 15:07

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

clang-tidy made some suggestions

There were too many comments to post at once. Showing the first 10 out of 14. Check the log or trigger a new build to see more.

Comment thread lib/Differentiator/ReverseModeVisitor.cpp Outdated
Comment thread lib/Differentiator/ReverseModeVisitor.cpp Outdated
Comment thread lib/Differentiator/ReverseModeVisitor.cpp Outdated
Comment thread lib/Differentiator/ReverseModeVisitor.cpp Outdated
Comment thread lib/Differentiator/ReverseModeVisitor.cpp Outdated
Comment thread lib/Differentiator/ReverseModeVisitor.cpp Outdated
Comment thread lib/Differentiator/ReverseModeVisitor.cpp Outdated
Comment thread lib/Differentiator/ReverseModeVisitor.cpp Outdated
Comment thread lib/Differentiator/ReverseModeVisitor.cpp Outdated

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

clang-tidy made some suggestions

Comment thread lib/Differentiator/ReverseModeVisitor.cpp Outdated
Comment thread lib/Differentiator/ReverseModeVisitor.cpp Outdated
Comment thread lib/Differentiator/ReverseModeVisitor.cpp Outdated
Comment thread lib/Differentiator/ReverseModeVisitor.cpp Outdated
Comment thread lib/Differentiator/ReverseModeVisitor.cpp Outdated
Comment thread lib/Differentiator/ReverseModeVisitor.cpp Outdated
Comment thread lib/Differentiator/ReverseModeVisitor.cpp Outdated
Comment thread lib/Differentiator/ReverseModeVisitor.cpp Outdated
Repository owner deleted a comment from github-actions Bot Jul 10, 2025
Repository owner deleted a comment from github-actions Bot Jul 10, 2025

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

clang-tidy made some suggestions

Comment thread lib/Differentiator/ReverseModeVisitor.cpp Outdated
Comment thread lib/Differentiator/ReverseModeVisitor.cpp Outdated
Comment thread lib/Differentiator/ReverseModeVisitor.cpp Outdated
Comment thread lib/Differentiator/ReverseModeVisitor.cpp Outdated
Comment thread lib/Differentiator/ReverseModeVisitor.cpp Outdated

@kchristin22 kchristin22 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I think this approach is great, we can cover the const cases now and extend it to re-assignments later. This could be done by adding the decl refs of indexes that are injective to a list as we traverse them in the Inject checker and then update these decl refs when we ecnounter an assignment in the original visitor (reverse mode visitor). We don't have to store them in this PR, it could be left for the re-assignment one.

Comment thread test/CUDA/GradientKernels.cu
Comment thread lib/Differentiator/ReverseModeVisitor.cpp Outdated

bool isInjectiveIdx(const clang::Expr* E) {
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast)
TraverseStmt(const_cast<clang::Expr*>(E));

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Can we avoid const_casts its UB.

} else if (const auto* ASE = dyn_cast<ArraySubscriptExpr>(E)) {
const auto* base =
dyn_cast<DeclRefExpr>(ASE->getBase()->IgnoreImpCasts());
if (const auto* PVD = dyn_cast<ParmVarDecl>(base->getDecl())) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Checking if it's a parameter is common in both branches (DRE and ASE), so we should have that as the outer if-condition

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

clang-tidy made some suggestions


static bool isInjectiveE(const clang::Expr* E) {
class InjectiveCheckerExpr
: public clang::RecursiveASTVisitor<InjectiveCheckerExpr> {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

warning: no header providing "clang::RecursiveASTVisitor" is directly included [misc-include-cleaner]

lib/Differentiator/ReverseModeVisitor.cpp:52:

- #include <cstddef>
+ #include <clang/AST/RecursiveASTVisitor.h>
+ #include <cstddef>

return true;
if (const auto* DRE = dyn_cast<DeclRefExpr>(E)) {
if (const auto* PVD = dyn_cast<ParmVarDecl>(DRE->getDecl())) {
if (m_DiffReq->hasAttr<clang::CUDAGlobalAttr>())

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

warning: no header providing "clang::CUDAGlobalAttr" is directly included [misc-include-cleaner]

lib/Differentiator/ReverseModeVisitor.cpp:52:

- #include <cstddef>
+ #include <clang/AST/Attrs.inc>
+ #include <cstddef>

@ovdiiuv
ovdiiuv force-pushed the atomics-ana branch 3 times, most recently from be9b58a to 72a6131 Compare July 21, 2025 13:40
@vgvassilev

Copy link
Copy Markdown
Owner

@kchristin22, is this good to go?

public:
InjectiveCheckerExpr() = default;

bool comparePatternToTree(const IdxNode* current,

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

We need some more documentation about what this routine does.

const std::vector<std::string>& patternIdx,
size_t i = 1) {

if (!current && (i >= patternIdx.size() || patternIdx[i].empty()))

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Under what conditions current can be a nullptr?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Each node has a left and right children whatsoever, those might be not initialized.

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

In that case, I'd move the

          return false;

before that statement and remove the !current checks from the rest.

return true;
}

bool TraverseDeclRefExpr(clang::DeclRefExpr* DRE) {

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Why not VisitDeclRefExpr but TraverseDeclRefExpr?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

I think we have converged that traverse methods are better, since they do not visit subexpressions automatically, meaning if x=y is visited and not traversed, we would visit LHS, RHS and then x, y as subexpressions of x=y.

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Can we capture that as a comment in the appropriate place..

Comment thread lib/Differentiator/ReverseModeVisitor.cpp Outdated

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

clang-tidy made some suggestions


[[nodiscard]] bool isExpr() const { return m_Kind == Kind::Expr; }
[[nodiscard]] bool isOpcode() const { return m_Kind == Kind::Opcode; }
};

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

warning: enum 'side' uses a larger base type ('int', size: 4 bytes) than necessary for its value set, consider using 'std::uint8_t' (1 byte) as the base type to reduce its size [performance-enum-size]

      enum class side { left, right } m_Side;
                 ^

};

ExprOrBinOp Node;
std::unique_ptr<IdxNode> left;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

warning: constructor does not initialize these fields: m_Side [cppcoreguidelines-pro-type-member-init]

      InjectiveCheckerExpr() = default;
      ^


if (m_Side == side::right)
m_ParentNode->right = std::move(curr);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

warning: member 'm_Context' of type 'clang::ASTContext &' is a reference [cppcoreguidelines-avoid-const-or-ref-data-members]

      clang::ASTContext& m_Context;
                         ^

return false;

if (!isConstL && isConstR)
return TraverseStmt(L);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

warning: method 'TraverseDeclRefExpr' can be made static [readability-convert-member-functions-to-static]

Suggested change
return TraverseStmt(L);
static bool TraverseDeclRefExpr(clang::DeclRefExpr* DRE) {

Comment thread lib/Differentiator/ReverseModeVisitor.cpp Outdated
Comment thread lib/Differentiator/ReverseModeVisitor.cpp Outdated

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

clang-tidy made some suggestions

There were too many comments to post at once. Showing the first 10 out of 15. Check the log or trigger a new build to see more.

Comment thread lib/Differentiator/CladUtils.cpp
Comment thread lib/Differentiator/CladUtils.cpp
Comment thread lib/Differentiator/CladUtils.cpp
Comment thread lib/Differentiator/CladUtils.cpp
Comment thread lib/Differentiator/CladUtils.cpp
Comment thread lib/Differentiator/CladUtils.cpp
Comment thread lib/Differentiator/CladUtils.cpp
Comment thread lib/Differentiator/CladUtils.cpp

bool TraverseBinaryOperator(clang::BinaryOperator* BinOp) {
const auto opCode = BinOp->getOpcode();
if (opCode == BO_Add || opCode == BO_Mul) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

warning: no header providing "clang::BO_Add" is directly included [misc-include-cleaner]

          if (opCode == BO_Add || opCode == BO_Mul) {
                        ^


bool TraverseBinaryOperator(clang::BinaryOperator* BinOp) {
const auto opCode = BinOp->getOpcode();
if (opCode == BO_Add || opCode == BO_Mul) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

warning: no header providing "clang::BO_Mul" is directly included [misc-include-cleaner]

          if (opCode == BO_Add || opCode == BO_Mul) {
                                            ^

Comment thread include/clad/Differentiator/CladUtils.h Outdated

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

clang-tidy made some suggestions

IdxNode* currPtr = nullptr;

if (!m_Root) {
m_Root = std::move(curr);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

warning: no header providing "std::move" is directly included [misc-include-cleaner]

lib/Differentiator/CladUtils.cpp:21:

- #include <vector>
+ #include <utility>
+ #include <vector>

bool isInjective(const clang::Expr* E, clang::ASTContext& ctx) {
class InjectiveChecker
: public clang::RecursiveASTVisitor<InjectiveChecker> {
clang::ASTContext& m_Context;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

warning: member 'm_Context' of type 'clang::ASTContext &' is a reference [cppcoreguidelines-avoid-const-or-ref-data-members]

        clang::ASTContext& m_Context;
                           ^

return false;
}

bool TraverseDeclRefExpr(clang::DeclRefExpr* DRE) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

warning: method 'TraverseDeclRefExpr' can be made static [readability-convert-member-functions-to-static]

Suggested change
bool TraverseDeclRefExpr(clang::DeclRefExpr* DRE) {
static bool TraverseDeclRefExpr(clang::DeclRefExpr* DRE) {

return false;
}

bool TraverseIntegerLiteral(clang::IntegerLiteral* IL) { return false; }

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

warning: method 'TraverseIntegerLiteral' can be made static [readability-convert-member-functions-to-static]

Suggested change
bool TraverseIntegerLiteral(clang::IntegerLiteral* IL) { return false; }
static bool TraverseIntegerLiteral(clang::IntegerLiteral* IL) { return false; }

@ovdiiuv
ovdiiuv force-pushed the atomics-ana branch 2 times, most recently from a716646 to a827622 Compare August 3, 2025 18:27
Before this PR, we unconditionally produced atomic operations for writes to global gpu memory. For example, the gradient of the kernel below:

```cpp

__global__ void indices_perm(int *out, int *in) {
   int index = threadIdx.x + blockIdx.x * blockDim.x;
   out[index] += in[index];
 }
```

used to have an `atomicAdd` in the reverse pass corresponding to the `out[index] += in[index]` statement. The way `index` is defined makes it injective, meaning no two threads in any two blocks would share the same `index`. In cases like this no data races occur, so we can avoid using atomic operations.

@vgvassilev vgvassilev left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

LGTM!

@vgvassilev
vgvassilev merged commit 261287b into vgvassilev:master Aug 4, 2025
85 checks passed
@ovdiiuv
ovdiiuv deleted the atomics-ana branch August 5, 2025 21:00
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.

3 participants