Skip to content

[HLSL] Add MatVec interpretation and bias coverage for LinAlg - #8775

Merged
Jack Elliott (JoeCitizen) merged 6 commits into
microsoft:mainfrom
JoeCitizen:linalg-hlk-matvec-interpretation
Aug 18, 2026
Merged

[HLSL] Add MatVec interpretation and bias coverage for LinAlg#8775
Jack Elliott (JoeCitizen) merged 6 commits into
microsoft:mainfrom
JoeCitizen:linalg-hlk-matvec-interpretation

Conversation

@JoeCitizen

@JoeCitizen Jack Elliott (JoeCitizen) commented Aug 13, 2026

Copy link
Copy Markdown
Collaborator

Proposal 0035 permits a thread-scope Multiply vector to be native or an InterpretedVector, pairing a packed vector with an interpretation type. The interpreted forms had no coverage.

These six tests cover non-uniform F16 in row-major and column-major layouts, packed SInt8 and UInt8 inputs, unsigned UInt32 output, and MultiplyAdd with the bias in a separate resource. The UInt8 case reuses the SInt8 bytes so high-bit lanes decode differently under the two interpretations. Expected values come from overflow-checked host dot products, and the full output allocation including padding and guard bytes is compared against a poison fill.

Column-major is required rather than gated: a thread-scope load permits row-major, column-major and optimal layouts, and only transposed loads need a driver query.

Copilot AI balanced review requested due to automatic review settings August 13, 2026 21:03

Copilot AI 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.

Pull request overview

Adds thread-scope LinAlg matrix-vector tests for layouts, interpreted inputs, unsigned output, and independent bias.

Changes:

  • Adds six Multiply/MultiplyAdd execution tests.
  • Adds host-side encoding, overflow-checked oracles, capability checks, and guard-byte validation.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread tools/clang/unittests/HLSLExec/LinAlgTests.cpp Outdated
Copilot AI review requested due to automatic review settings August 13, 2026 21:58
@JoeCitizen
Jack Elliott (JoeCitizen) force-pushed the linalg-hlk-matvec-interpretation branch from baa671f to 2010c0e Compare August 13, 2026 21:58

Copilot AI 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.

Pull request overview

Copilot reviewed 1 out of 1 changed files in this pull request and generated no new comments.

@damyanp Damyan Pepper (damyanp) 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 observations, none of these should block the PR after it has been reviewed by someone with more domain knowledge than me.

Comment thread tools/clang/unittests/HLSLExec/LinAlgTests.cpp Outdated
Comment thread tools/clang/unittests/HLSLExec/LinAlgTests.cpp Outdated
Comment thread tools/clang/unittests/HLSLExec/LinAlgTests.cpp Outdated
Copilot AI review requested due to automatic review settings August 14, 2026 20:33

Copilot AI 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.

Pull request overview

Copilot reviewed 1 out of 1 changed files in this pull request and generated no new comments.

Suppressed comments (2)

tools/clang/unittests/HLSLExec/LinAlgTests.cpp:2287

  • Please use the explicit result type under the repository's almost-never-auto convention. runShaderOp declares this as std::shared_ptr<st::ShaderOpTestResult> in HlslExecTestUtils.h:601.
  auto Result =

tools/clang/unittests/HLSLExec/LinAlgTests.cpp:2272

  • Per the repository's almost-never-auto convention, this straightforward return type should be explicit. createComputeOp is declared as returning std::unique_ptr<st::ShaderOp> in HlslExecTestUtils.h:571.

This issue also appears on line 2287 of the same file.

  auto Op = createComputeOp(Shader, "cs_6_10", RootSignature, Args->c_str());

Copilot AI review requested due to automatic review settings August 14, 2026 22:06

Copilot AI 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.

Pull request overview

Copilot reviewed 1 out of 1 changed files in this pull request and generated no new comments.

Jack Elliott and others added 4 commits August 15, 2026 10:34
Exercise non-uniform F16 row-major and column-major layouts, packed SInt8 and
UInt8 interpreted inputs, unsigned UInt32 output, and a separate non-uniform
bias resource. High-bit UInt8 lanes distinguish unsigned from signed decoding
of the same bytes.

Derive every expected result with overflow-checked host dot products plus
optional bias, encode packed lanes least-significant-byte first, and compare
the complete poisoned output including padding and guard bytes. Query the
exact vector, matrix, bias, and result capability tuple, requiring both
mandatory F16 layouts and gating only optional output cases.

Column-major is required rather than capability gated because a thread scope
matrix load permits row-major, column-major and optimal layouts, and only
transposed loads are implementation specific and need a driver query.

Assisted-by: GitHub Copilot

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 83725f5d-8e98-4c1d-91ee-ad47629e007b
LinAlgTests.cpp places helper namespaces before the test classes that use
them: cpu_oracle sits at the top of the file, ahead of the first test class.
matvec_interpretation was appended after the classes instead, which left no
way for a LinAlgCPUOracleTests method to call into it without a forward
declaration.

This is a pure relocation of the namespace block. No line is added, removed
or edited: the file has the same 6060 lines before and after, and the sorted
set of lines is identical. clang-format reports no drift.

Verified with the full HLSLExec LinAlg selection on WARP, compared per test
rather than by totals: 45 total, 39 passed, 5 failed, 1 skipped, with the
non-passing set unchanged from the parent commit.

Assisted-by: GitHub Copilot
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 83725f5d-8e98-4c1d-91ee-ad47629e007b
Move the checked int64 helpers into cpu_oracle, next to the existing
checkedMultiply and checkedAdd for size_t. That namespace is already the
file's home for arithmetic the oracle relies on, so matvec_interpretation no
longer carries its own copies. The checks stay: encodeComponents accepts
UInt32 values up to 4294967295, so a UInt32 matrix times a UInt32 vector
overflows int64 by construction. An overflow in the oracle yields a wrong
expected value, which can pass a broken implementation rather than fail a
correct one, so this is the direction worth guarding.

Report the rejected value when a component cannot be represented in the
target type. All six rejection sites now name the type and the value instead
of failing with no context, and componentTypeName covers SInt8 and UInt8
rather than returning "Unsupported" for them.

Promote the oracle self-test out of runCase into its own method on
LinAlgCPUOracleTests, so it runs once instead of once per case. That class
already exists for exactly this purpose and deliberately carries no Kits
metadata, so HLK runs never select it. Each assertion gets its own VERIFY,
replacing a single six-term conjunction that could not say which part failed.

Verified with the full HLSLExec LinAlg selection on WARP, compared per test
rather than by totals: 46 total, 40 passed, 5 failed, 1 skipped. The only
difference from the parent commit is the added MatVecHostOracle passing; the
non-passing set is unchanged.

Assisted-by: GitHub Copilot
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 83725f5d-8e98-4c1d-91ee-ad47629e007b
Follows the same review feedback Damyan and Chris gave on microsoft#8774, applied here
so the two pull requests stay consistent and so neither lands this code.

Every value these helpers guarded is authored by the test: dimensions are
literals of at most M=16 and N=16, and the largest integer literal in the file
is 65504. The widest accumulation the oracle can perform is far below the
int64 range, so the overflow branches were unreachable, and an overflow would
have indicated a bug in the test rather than a driver failing conformance.

checkedAddInt64 and checkedMultiplyInt64 are removed together with the two
self-test assertions that existed only to exercise them. calculateExpected
returns its result directly rather than an optional and reads as ordinary
arithmetic. Its size preconditions are already established by isCaseValid,
which runCase verifies before any of this is reached.

This also removes a collision that neither pull request shows in its own diff.
microsoft#8774 defines the same two helpers in the same cpu_oracle namespace but in a
different region of the file, so git would have merged both without conflict
and left main with a duplicate definition.

Verified with the full HLSLExec LinAlg selection on WARP, compared per test
rather than by totals: 46 total, 40 passed, 5 failed, 1 skipped, identical to
the parent commit.

Assisted-by: GitHub Copilot
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 83725f5d-8e98-4c1d-91ee-ad47629e007b
Copilot AI review requested due to automatic review settings August 14, 2026 22:47
@JoeCitizen
Jack Elliott (JoeCitizen) force-pushed the linalg-hlk-matvec-interpretation branch from c89b779 to 9b8b5b5 Compare August 14, 2026 22:47

Copilot AI 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.

Pull request overview

Copilot reviewed 1 out of 1 changed files in this pull request and generated no new comments.

Suppressed comments (2)

tools/clang/unittests/HLSLExec/LinAlgTests.cpp:1924

  • The host oracle performs the dot product with unchecked signed int64_t multiplication and addition. A valid supported case using large U32 operands can therefore trigger signed-overflow undefined behavior and produce unreliable expected bytes, contrary to the PR description's overflow-checked oracle claim. Please use checked or wider arithmetic and reject results that cannot be represented before encoding them.
      Expected[Row] +=
          Case.MatrixValues[static_cast<size_t>(Row) * Case.N + Column] *
          Case.InterpretedVectorValues[Column];

tools/clang/unittests/HLSLExec/LinAlgTests.cpp:1811

  • This round-trip check can itself invoke undefined behavior: INT64_MAX rounds to 2^63 when converted to float, and converting that out-of-range float back to int64_t is undefined. Check that FloatValue is within the half-open int64_t range before performing the cast, then do the exactness comparison.

This issue also appears on line 1922 of the same file.

      if (static_cast<int64_t>(FloatValue) != Value)
        return reportUnrepresentable(Type, Value);

@damyanp Damyan Pepper (damyanp) 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 - my previous comments are all addressed.

Comment thread tools/clang/unittests/HLSLExec/LinAlgTests.cpp
Comment thread tools/clang/unittests/HLSLExec/LinAlgTests.cpp Outdated
Comment thread tools/clang/unittests/HLSLExec/LinAlgTests.cpp
Comment thread tools/clang/unittests/HLSLExec/LinAlgTests.cpp Outdated
Comment thread tools/clang/unittests/HLSLExec/LinAlgTests.cpp Outdated
Comment thread tools/clang/unittests/HLSLExec/LinAlgTests.cpp Outdated
Comment thread tools/clang/unittests/HLSLExec/LinAlgTests.cpp
Ashley asked that anything switching over ComponentType list every case we care
about rather than lean on a default arm. The authoritative set is the fourteen
types ComponentTypeTraits declares in hlsl/dx/linalg.h, which is narrower than
the twenty four in DxilConstants.h: it excludes I1, the SNorm and UNorm
variants, PackedS8x32 and PackedU8x32. componentByteSize, storageTypeName and
isPackedByteVector now enumerate those fourteen, and componentByteSize answers
the question the review opened with by returning 8 for I64, U64 and F64.

storageElementCount no longer hardcodes 3 and 4. The packing factor comes from a
new elementsPerScalar that mirrors ComponentTypeTraits::ElementsPerScalar, and
Ashley's point about BFloat16 holds: it is two elements per scalar, not four. It
is not derivable from the byte size either, since F16 and BFloat16 are both two
bytes but only F16 has a native HLSL scalar. isPackedByteVector stays byte only
for that same reason, because encodeVectorBuffer routes on it and
encodePackedVector packs four bytes to a uint, which would corrupt BFloat16.

isCaseValid is now a sequence of grouped guards covering dimensions, input
counts, layout, component types, vector form and bias, rather than one nine term
conjunction. Its checkedMultiply is gone: MatrixDim is uint32_t, so widening the
row by column product to uint64_t cannot overflow. The remaining checked
arithmetic this branch introduced is removed on the same grounds as the parent
commit, which leaves the file free of qualified cpu_oracle::checked calls. The
size_t helpers those calls reached into are pre-existing in main and are a
separate cleanup.

Unknown types no longer fail quietly. componentByteSize, elementsPerScalar and
storageTypeName name the offending type and fail, because each maps a type onto
a value its caller needs, so a missing answer is a defect rather than a result.
The report includes the numeric enum value, since componentTypeName only knows
seven types and prints Unsupported for the rest. isPackedByteVector and
isEncodableComponentType stay silent because false is a legitimate answer for
them. storageTypeName separates the two failures: a valid matrix type the
encoder cannot drive yet reports as unsupported, while anything outside the
fourteen reports as unexpected. This matters because isCaseValid has a single
caller, so the previous behaviour was a generic invalid case failure that did
not say which field was at fault.

Verified with the full HLSLExec LinAlg selection on WARP, compared per test
rather than by totals: 50 total, 44 passed, 5 failed, 1 skipped, identical to
the parent commit in every entry. Both the baseline and this change were
measured on the same experimental tier D3D12 runtime, because a default tier
runtime cannot enable SM 6.10 and blocks the whole selection. The assertions
were confirmed to fire by temporarily calling the helpers with PackedS8x32 and
SNormF32 and observing the logged type and the failed test.

Assisted-by: GitHub Copilot
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 83725f5d-8e98-4c1d-91ee-ad47629e007b
Copilot AI review requested due to automatic review settings August 18, 2026 00:05

Copilot AI 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.

Pull request overview

Copilot reviewed 1 out of 1 changed files in this pull request and generated no new comments.

Suppressed comments (1)

tools/clang/unittests/HLSLExec/LinAlgTests.cpp:2081

  • This comment says there are four guard bytes, but OutputGuardBytes is 16. Refer to the guard region instead so the documentation remains accurate if its size changes.
  // Round the byte count up to a whole number of 4-byte words so that the four
  // guard bytes start on a word boundary. Both sizes are bounded by the matrix

Comment thread tools/clang/unittests/HLSLExec/LinAlgTests.cpp
Comment thread tools/clang/unittests/HLSLExec/LinAlgTests.cpp Outdated
The comment above the output buffer sizing said the rounding aligned "the
four guard bytes", but OutputGuardBytes is 16 and has been since this
change was first written. The wording conflated the 4-byte word alignment
being applied with the size of the guard region itself.

Refer to the guard region rather than a byte count so the comment stays
accurate if OutputGuardBytes ever changes.

Comment only, no functional change. The full HLSLExec LinAlg suite is
unchanged per test on a current D3D12 runtime (50 total, 49 passed,
0 failed, 1 skipped).

Assisted-by: GitHub Copilot
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 83725f5d-8e98-4c1d-91ee-ad47629e007b
Copilot AI review requested due to automatic review settings August 18, 2026 18:56

@V-FEXrt Ashley Coleman (V-FEXrt) 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.

LGTM, but not actually clicking the button since auto merge is on and other folks left new comments

Copilot AI 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.

Pull request overview

Copilot reviewed 1 out of 1 changed files in this pull request and generated no new comments.

Suppressed comments (2)

tools/clang/unittests/HLSLExec/LinAlgTests.cpp:2046

  • This validation only rejects mismatched interpretations for F32 and packed-byte inputs. Native F16, I32, and U32 cases can therefore pass with a different interpretation even though encodeVectorBuffer emits N native scalars, which does not represent the packed logical lane count. Since this harness models packed inputs by using the logical packed type as VectorInputType, require the interpretation to match for every supported case.
  if (Case.VectorInputType == ComponentType::F32 &&
      Case.InputInterpretation != ComponentType::F32)
    return false;
  if (isPackedByteVector(Case.VectorInputType) &&
      Case.InputInterpretation != Case.VectorInputType)

tools/clang/unittests/HLSLExec/LinAlgTests.cpp:2005

  • calculateExpected is not overflow-checked: valid U32 operands can exceed int64_t in a single product, and repeated I32/U32 products or the bias addition can overflow the accumulator. Signed overflow is undefined behavior, so the oracle can produce an incorrect expected buffer instead of rejecting an unrepresentable case. Please use checked multiply/add operations (and propagate failure to encodeExpectedOutput) as described by the PR.
      Expected[Row] +=
          Case.MatrixValues[static_cast<size_t>(Row) * Case.N + Column] *
          Case.InterpretedVectorValues[Column];
    if (Case.hasBias())
      Expected[Row] += Case.BiasValues[Row];

@damyanp Damyan Pepper (damyanp) 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.

Approving - comments all seem to be addressed and there's multiple LGTM's.

@JoeCitizen
Jack Elliott (JoeCitizen) merged commit 3bc198b into microsoft:main Aug 18, 2026
13 checks passed
@github-project-automation github-project-automation Bot moved this from New to Done in HLSL Roadmap Aug 18, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

5 participants