Skip to content

Support runtime GEMM dimensions for dynamic shapes - #11

Open
harz05 wants to merge 12 commits into
ML4EP:devfrom
harz05:fix/dynamic-gemm-layouts
Open

Support runtime GEMM dimensions for dynamic shapes#11
harz05 wants to merge 12 commits into
ML4EP:devfrom
harz05:fix/dynamic-gemm-layouts

Conversation

@harz05

@harz05 harz05 commented Jul 24, 2026

Copy link
Copy Markdown

Implements #10

Edit:

The PR has been brought up to date with the dev branch and the approach has grown from the older description, described below.

The layout part is unchanged: one cublasLtMatrixLayout_t per role, runtime dims stamped in before each multiply operation. The change is on the algorithm caching. Re-querying the heuristic per new size meant a dynamic model pays one heuristic query for every distinct size it meets and the cache grows with the sweep. Now addLayoutConfig is no longer a no-op: it records the call site's construct-time shape as its envelope and resolves the algorithm for it once, up front. At infer time any size covered by an envelope reuses that entry; so a size sweep does zero heuristic queries and adds zero cache entries. In any case if cuBLASLt cannot run the envelope's algorithm at the actual size the call falls back to resolving that exact shape.
LayoutStats and algoCacheSize() expose the counts that the tests assert through them.

The cache is unbounded by default. Passing a limit to the constructor enables LRU eviction, for the case where many above-envelope shapes get resolved individually.

Benchmark (Nvidia H100) through exisiting, bench_cuda:
image


Original description:

sofieBLAS registers cuBLASLt matrix layouts at construct size (addLayoutConfig) and the matmul path looks them up by the runtime (rows, cols). With dynamic shapes the two disagree i.e. a Session constructed at one size and inferred at another misses the layout and throws std::out_of_range, so one Session only works at a single size.

This PR resolves each matrix's layout at matmul time instead: keep one cublasLtMatrixLayout_t per role (A/B/C) and stamp the runtime dims into it via cublasLtMatrixLayoutSetAttribute before each multiply. The descriptor is host-side metadata consumed by cublasLtMatmul at the call, so reusing one object across shapes is safe.


@harz05
harz05 force-pushed the fix/dynamic-gemm-layouts branch from 6edd3e3 to 21f938d Compare August 4, 2026 10:58
@sanjibansg

Copy link
Copy Markdown
Member

/runtest h100

@github-actions

Copy link
Copy Markdown

/runtest (h100): triggered - view run

@sanjibansg

Copy link
Copy Markdown
Member

/runtest mi300x

@github-actions

Copy link
Copy Markdown

/runtest (mi300x): triggered - view run

@github-actions

Copy link
Copy Markdown

/runtest (h100): GPU Unit Tests ✅ passed - view run

@sanjibansg

Copy link
Copy Markdown
Member

/runtest w7900

@github-actions

Copy link
Copy Markdown

/runtest (w7900): triggered - view run

@github-actions

Copy link
Copy Markdown

/runtest (w7900): GPU Unit Tests ✅ passed - view run

@sanjibansg sanjibansg left a comment

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.

LGTM!
One last comment, can you add a bit of documentation mentioning details like how to initialize the cache, etc.

@harz05

harz05 commented Aug 18, 2026

Copy link
Copy Markdown
Author

As requested, added a README section covering the dynamic-shape behavior and how to initialize the cache limit
@sanjibansg

@harz05

harz05 commented Aug 19, 2026

Copy link
Copy Markdown
Author

hipBLASLt was missing the same implementation that was done for cuBLASLt, so this PR includes that as well. The only difference is that hipBLASLt has no hipblasLtMatmulAlgoCheck, so the exact-shape fallback uses hipblaslt_ext::matmulIsAlgoSupported instead.

Tests pass on AMD, MI100

@sanjibansg sanjibansg left a comment

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.

Some more comments on more descriptive methods and algorithmic reasoning. Comments mostly addresses the cuBLASLt code, but stand similar for the hipBLASLt ones as well.

Comment thread README.md Outdated

## Dynamic GEMM shapes and the algorithm cache

Both GPU backends behave the same way here. One instance serves GEMM calls at sizes that vary at runtime: matrix layouts are not tied to a shape, and each call stamps its dimensions into a shared per-role descriptor right before the multiply.

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.

Using Both GPU Backends is ambiguous, better state clearly what it infers here.

Comment thread README.md Outdated

Both GPU backends behave the same way here. One instance serves GEMM calls at sizes that vary at runtime: matrix layouts are not tied to a shape, and each call stamps its dimensions into a shared per-role descriptor right before the multiply.

Algorithm selection is cached. `addLayoutConfig(m, n, k, lda, ldb, ldc, transa, transb)` declares the largest shape a call site will use (its envelope) and resolves the algorithm for it once, up front. Any later call at a size covered by an envelope reuses that entry, so sweeping sizes does not re-query the heuristic or grow the cache. A call with no covering envelope is resolved at its exact shape and cached per shape, and if an envelope's algorithm cannot run a particular size the call falls back to exact-shape resolution.

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.

This paragraph is not very clear as to what it addresses. We need to state clearly what Algorithm selection here means, what envelope here is, etc. Code documentation are better descriptive of the algorithms, features, and declarations it entails

if (!L) {
CHECK_CUBLAS(cublasLtMatrixLayoutCreate(&L, CUDA_R_32F, rows, cols, ld));
} else {
CHECK_CUBLAS(cublasLtMatrixLayoutSetAttribute(

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.

what if there is only a change in rows, but the other fields remain unaffected, do we still need to update all the attributes?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Right now all three attributes are rewritten on every call even when nothing changed. I'm planning to keep track of the dimensions each descriptor currently holds and update only what differs i.e. a change in rows updates rows and ld together (ld=rows), a change in cols updates only cols and a repeated shape writes nothing.

However the above approach will only avoid rewrites when the same size repeats in succession, so a better approach could be that we give each call site declared through addLayoutConfig its own three descriptors instead of sharing one per matrix role. Sites then stop overwriting each other, so when sizes repeat across events nothing is written at all and the ordering constraint between the validity check and the final stamp mostly goes away; so I think we can try this instead

Comment thread include/sofieBLAS/backends/cuda/sofieBLAS_cublas.hpp Outdated
const ShapeEnvelope *
findEnvelope(const std::pair<std::size_t, std::size_t> &kA,
const std::pair<std::size_t, std::size_t> &kB,
const std::pair<std::size_t, std::size_t> &kC) const {

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.

explain what this method is exactly doing

Comment thread include/sofieBLAS/backends/cuda/sofieBLAS_cublas.hpp Outdated

if (env && !algoUsable(desc, h.algo, kA, kB, kC)) {
++stats.envelopeRejects;
h = *getOrComputeAlgo(transA, transB, epilogue, kA, kB, kC);

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.

why do we need to compute the algo twice here?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

it's not computed twice in the normal path. The second resolution happens only when cuBLASLt rejects the declared shape's algorithm at this call's exact size (returns NOT_SUPPORTED, m=1 was found to do this for cuBLASLt) and then it falls back to resolving at the exact shape, which also gets cached. Added a comment explaining the same

std::unordered_map<std::pair<std::size_t, std::size_t>,
hipblasLtMatrixLayout_t, PairHash, PairEq>
layoutStore;
enum LayoutRole { ROLE_A = 0, ROLE_B = 1, ROLE_C = 2 };

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.

add a comment as to what LayoutRole means and what those individual roles mean here.

using Operation = hipblasOperation_t;
using Epilogue = hipblasLtEpilogue_t;

static constexpr auto OpN = HIPBLAS_OP_N;

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

constexpr benchamark

@harz05

harz05 commented Aug 28, 2026

Copy link
Copy Markdown
Author

@sanjibansg
Following are the benchmarking results for the new changes on both CUDA and HIP (using static constexpr members for cuda and hip)-

image image

All the existing tests present in dev are also passing

Nvidia: H100
AMD: MI100

@sanjibansg

Copy link
Copy Markdown
Member

/runtest h100-47gb

@github-actions

github-actions Bot commented Sep 2, 2026

Copy link
Copy Markdown

/runtest (h100-47gb): triggered - view run

@sanjibansg

Copy link
Copy Markdown
Member

/runtest mi300x

@github-actions

github-actions Bot commented Sep 2, 2026

Copy link
Copy Markdown

/runtest (mi300x): triggered - view run

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