Files
derek.holloway 7502bf64a4
CodeQL / Analyze (actions) (push) Canceled after 0s
CodeQL / Analyze (python) (push) Canceled after 0s
pre-commit / pre-commit (push) Canceled after 0s
Multi-Arch CI / setup (push) Canceled after 0s
Publish build_manylinux_x86_64 images / publish_build_manylinux_x86_64 (push) Canceled after 0s
Setup (Multi-Arch) / setup (push) Canceled after 0s
Multi-Arch CI / Linux::${{ fromJSON(needs.setup.outputs.linux_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubi10 image / publish_no_rocm_image_ubi10 (push) Canceled after 0s
Multi-Arch CI / Windows::${{ fromJSON(needs.setup.outputs.windows_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Multi-Arch CI / Manifest Diff (push) Canceled after 0s
TheRock Manifest Diff Report / Generate Manifest Diff Report (push) Canceled after 0s
Multi-Arch CI / CI Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_ocl_rt images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_rocgdb images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Unit Tests / Unit Tests :: ubuntu-24.04 (push) Canceled after 0s
Unit Tests / Unit Tests :: windows-2022 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_media (push) Canceled after 0s
Unit Tests / Unit Tests Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_openmpi (push) Canceled after 0s
Flatten subprojects into a single base monorepo
2026-09-02 16:34:47 -07:00

577 lines
19 KiB
C++

// Copyright © Advanced Micro Devices, Inc., or its affiliates.
// SPDX-License-Identifier: MIT
#include <gtest/gtest.h>
#include <hipdnn_frontend/Error.hpp>
#include <hipdnn_frontend/attributes/GraphAttributes.hpp>
#include <hipdnn_frontend/attributes/SdpaAttributes.hpp>
#include <hipdnn_frontend/attributes/TensorAttributes.hpp>
#include <hipdnn_frontend/node/SdpaFwdNode.hpp>
using namespace hipdnn_frontend;
using namespace hipdnn_frontend::graph;
namespace
{
// Helper: create a rank-4 tensor with given dims [batch, heads, seq, head_dim]
std::shared_ptr<TensorAttributes>
makeTensor4D(int64_t batch, int64_t heads, int64_t seq, int64_t headDim)
{
auto t = std::make_shared<TensorAttributes>();
t->set_dim({batch, heads, seq, headDim});
return t;
}
SdpaAttributes makeMinimalAttrs(const std::shared_ptr<TensorAttributes>& q,
const std::shared_ptr<TensorAttributes>& k,
const std::shared_ptr<TensorAttributes>& v)
{
SdpaAttributes attrs;
attrs.set_q(q);
attrs.set_k(k);
attrs.set_v(v);
attrs.set_o(std::make_shared<TensorAttributes>());
return attrs;
}
} // namespace
TEST(TestSdpaFwdNode, PreValidateSucceedsMinimal)
{
// batch=2, num_heads=8, seq_q=16, head_dim=64
// num_kv_heads=8, seq_kv=32
auto q = makeTensor4D(2, 8, 16, 64);
auto k = makeTensor4D(2, 8, 32, 64);
auto v = makeTensor4D(2, 8, 32, 64);
auto attrs = makeMinimalAttrs(q, k, v);
const GraphAttributes graphAttrs;
const SdpaFwdNode node(std::move(attrs), graphAttrs);
auto err = node.pre_validate_node();
EXPECT_EQ(err.code, error_code_t::OK) << err.err_msg;
}
TEST(TestSdpaFwdNode, PreValidateSucceedsGQA)
{
// num_heads=8, num_kv_heads=2 — 8 % 2 == 0, GQA valid
auto q = makeTensor4D(2, 8, 16, 64);
auto k = makeTensor4D(2, 2, 32, 64);
auto v = makeTensor4D(2, 2, 32, 64);
auto attrs = makeMinimalAttrs(q, k, v);
const GraphAttributes graphAttrs;
const SdpaFwdNode node(std::move(attrs), graphAttrs);
auto err = node.pre_validate_node();
EXPECT_EQ(err.code, error_code_t::OK) << err.err_msg;
}
TEST(TestSdpaFwdNode, PreValidateSucceedsMQA)
{
// MQA: num_kv_heads=1
auto q = makeTensor4D(2, 8, 16, 64);
auto k = makeTensor4D(2, 1, 32, 64);
auto v = makeTensor4D(2, 1, 32, 64);
auto attrs = makeMinimalAttrs(q, k, v);
const GraphAttributes graphAttrs;
const SdpaFwdNode node(std::move(attrs), graphAttrs);
auto err = node.pre_validate_node();
EXPECT_EQ(err.code, error_code_t::OK) << err.err_msg;
}
TEST(TestSdpaFwdNode, PreValidateSucceedsGQADifferentKVHeads)
{
// Q=32 heads, K=8, V=4 — independent GQA broadcast
auto q = makeTensor4D(2, 32, 128, 64);
auto k = makeTensor4D(2, 8, 128, 64);
auto v = makeTensor4D(2, 4, 128, 64);
auto attrs = makeMinimalAttrs(q, k, v);
const GraphAttributes graphAttrs;
const SdpaFwdNode node(std::move(attrs), graphAttrs);
auto err = node.pre_validate_node();
EXPECT_EQ(err.code, error_code_t::OK) << err.err_msg;
}
TEST(TestSdpaFwdNode, PreValidateFailsInvalidGQAVHeads)
{
// Q=8 heads, K=2 (valid), V=3 (8%3!=0)
auto q = makeTensor4D(2, 8, 16, 64);
auto k = makeTensor4D(2, 2, 32, 64);
auto v = makeTensor4D(2, 3, 32, 64);
auto attrs = makeMinimalAttrs(q, k, v);
const GraphAttributes graphAttrs;
const SdpaFwdNode node(std::move(attrs), graphAttrs);
auto err = node.pre_validate_node();
EXPECT_EQ(err.code, error_code_t::INVALID_VALUE);
}
TEST(TestSdpaFwdNode, PreValidateFailsZeroKvHeads)
{
// K has 0 heads — should fail positivity check before divisibility
auto q = makeTensor4D(2, 8, 16, 64);
auto k = makeTensor4D(2, 0, 32, 64);
auto v = makeTensor4D(2, 8, 32, 64);
auto attrs = makeMinimalAttrs(q, k, v);
const GraphAttributes graphAttrs;
const SdpaFwdNode node(std::move(attrs), graphAttrs);
auto err = node.pre_validate_node();
EXPECT_EQ(err.code, error_code_t::INVALID_VALUE);
}
TEST(TestSdpaFwdNode, PreValidateFailsZeroVHeads)
{
// V has 0 heads — should fail positivity check
auto q = makeTensor4D(2, 8, 16, 64);
auto k = makeTensor4D(2, 8, 32, 64);
auto v = makeTensor4D(2, 0, 32, 64);
auto attrs = makeMinimalAttrs(q, k, v);
const GraphAttributes graphAttrs;
const SdpaFwdNode node(std::move(attrs), graphAttrs);
auto err = node.pre_validate_node();
EXPECT_EQ(err.code, error_code_t::INVALID_VALUE);
}
TEST(TestSdpaFwdNode, PreValidateFailsInvalidGQAKHeads)
{
// Q=8 heads, K=3 (8%3!=0), V=4 (8%4==0) — K invalid, V valid
auto q = makeTensor4D(2, 8, 16, 64);
auto k = makeTensor4D(2, 3, 32, 64);
auto v = makeTensor4D(2, 4, 32, 64);
auto attrs = makeMinimalAttrs(q, k, v);
const GraphAttributes graphAttrs;
const SdpaFwdNode node(std::move(attrs), graphAttrs);
auto err = node.pre_validate_node();
EXPECT_EQ(err.code, error_code_t::INVALID_VALUE);
}
TEST(TestSdpaFwdNode, PreValidateFailsMissingQ)
{
SdpaAttributes attrs;
attrs.set_k(makeTensor4D(2, 8, 32, 64));
attrs.set_v(makeTensor4D(2, 8, 32, 64));
attrs.set_o(std::make_shared<TensorAttributes>());
const GraphAttributes graphAttrs;
const SdpaFwdNode node(std::move(attrs), graphAttrs);
auto err = node.pre_validate_node();
EXPECT_EQ(err.code, error_code_t::ATTRIBUTE_NOT_SET);
}
TEST(TestSdpaFwdNode, PreValidateFailsMissingK)
{
SdpaAttributes attrs;
attrs.set_q(makeTensor4D(2, 8, 16, 64));
attrs.set_v(makeTensor4D(2, 8, 32, 64));
attrs.set_o(std::make_shared<TensorAttributes>());
const GraphAttributes graphAttrs;
const SdpaFwdNode node(std::move(attrs), graphAttrs);
auto err = node.pre_validate_node();
EXPECT_EQ(err.code, error_code_t::ATTRIBUTE_NOT_SET);
}
TEST(TestSdpaFwdNode, PreValidateFailsMissingV)
{
SdpaAttributes attrs;
attrs.set_q(makeTensor4D(2, 8, 16, 64));
attrs.set_k(makeTensor4D(2, 8, 32, 64));
attrs.set_o(std::make_shared<TensorAttributes>());
const GraphAttributes graphAttrs;
const SdpaFwdNode node(std::move(attrs), graphAttrs);
auto err = node.pre_validate_node();
EXPECT_EQ(err.code, error_code_t::ATTRIBUTE_NOT_SET);
}
TEST(TestSdpaFwdNode, PreValidateFailsRankLessThan4)
{
// Q is rank-3, should fail
auto q = std::make_shared<TensorAttributes>();
q->set_dim({2, 8, 64}); // rank-3
auto k = makeTensor4D(2, 8, 32, 64);
auto v = makeTensor4D(2, 8, 32, 64);
auto attrs = makeMinimalAttrs(q, k, v);
const GraphAttributes graphAttrs;
const SdpaFwdNode node(std::move(attrs), graphAttrs);
auto err = node.pre_validate_node();
EXPECT_EQ(err.code, error_code_t::INVALID_VALUE);
}
TEST(TestSdpaFwdNode, PreValidateFailsRankGreaterThan4)
{
// Q is rank-5, should fail
auto q = std::make_shared<TensorAttributes>();
q->set_dim({1, 2, 8, 16, 64}); // rank-5
auto k = makeTensor4D(2, 8, 32, 64);
auto v = makeTensor4D(2, 8, 32, 64);
auto attrs = makeMinimalAttrs(q, k, v);
const GraphAttributes graphAttrs;
const SdpaFwdNode node(std::move(attrs), graphAttrs);
auto err = node.pre_validate_node();
EXPECT_EQ(err.code, error_code_t::INVALID_VALUE);
}
TEST(TestSdpaFwdNode, PreValidateSucceedsDifferentHeadDimV)
{
// V head_dim=32 differs from Q/K head_dim=64 — valid, headDimV is independent
auto q = makeTensor4D(2, 8, 16, 64);
auto k = makeTensor4D(2, 8, 32, 64);
auto v = makeTensor4D(2, 8, 32, 32);
auto attrs = makeMinimalAttrs(q, k, v);
const GraphAttributes graphAttrs;
const SdpaFwdNode node(std::move(attrs), graphAttrs);
auto err = node.pre_validate_node();
EXPECT_EQ(err.code, error_code_t::OK) << err.err_msg;
}
TEST(TestSdpaFwdNode, PreValidateFailsHeadDimMismatch)
{
// K head_dim=32, Q head_dim=64 — mismatch
auto q = makeTensor4D(2, 8, 16, 64);
auto k = makeTensor4D(2, 8, 32, 32);
auto v = makeTensor4D(2, 8, 32, 64);
auto attrs = makeMinimalAttrs(q, k, v);
const GraphAttributes graphAttrs;
const SdpaFwdNode node(std::move(attrs), graphAttrs);
auto err = node.pre_validate_node();
EXPECT_EQ(err.code, error_code_t::INVALID_VALUE);
}
TEST(TestSdpaFwdNode, PreValidateFailsOutputShapeMismatch)
{
// O shape [2, 8, 16, 99] but headDimV=64 — mismatch
auto q = makeTensor4D(2, 8, 16, 64);
auto k = makeTensor4D(2, 8, 32, 64);
auto v = makeTensor4D(2, 8, 32, 64);
SdpaAttributes attrs;
attrs.set_q(q);
attrs.set_k(k);
attrs.set_v(v);
auto o = makeTensor4D(2, 8, 16, 99); // wrong head_dim
attrs.set_o(o);
const GraphAttributes graphAttrs;
const SdpaFwdNode node(std::move(attrs), graphAttrs);
auto err = node.pre_validate_node();
EXPECT_EQ(err.code, error_code_t::INVALID_VALUE);
}
TEST(TestSdpaFwdNode, PreValidateSucceedsCorrectOutputShape)
{
// O shape matches [batch, num_heads, seq_q, headDimV]
auto q = makeTensor4D(2, 8, 16, 64);
auto k = makeTensor4D(2, 8, 32, 64);
auto v = makeTensor4D(2, 8, 32, 32); // headDimV=32
SdpaAttributes attrs;
attrs.set_q(q);
attrs.set_k(k);
attrs.set_v(v);
auto o = makeTensor4D(2, 8, 16, 32); // correct: matches headDimV
attrs.set_o(o);
const GraphAttributes graphAttrs;
const SdpaFwdNode node(std::move(attrs), graphAttrs);
auto err = node.pre_validate_node();
EXPECT_EQ(err.code, error_code_t::OK) << err.err_msg;
}
TEST(TestSdpaFwdNode, PreValidateFailsSeqKvMismatch)
{
// K seq_kv=32, V seq_kv=16 — mismatch
auto q = makeTensor4D(2, 8, 16, 64);
auto k = makeTensor4D(2, 8, 32, 64);
auto v = makeTensor4D(2, 8, 16, 64);
auto attrs = makeMinimalAttrs(q, k, v);
const GraphAttributes graphAttrs;
const SdpaFwdNode node(std::move(attrs), graphAttrs);
auto err = node.pre_validate_node();
EXPECT_EQ(err.code, error_code_t::INVALID_VALUE);
}
TEST(TestSdpaFwdNode, PreValidateFailsInvalidGQA)
{
// num_heads=8, num_kv_heads=3 — 8 % 3 != 0
auto q = makeTensor4D(2, 8, 16, 64);
auto k = makeTensor4D(2, 3, 32, 64);
auto v = makeTensor4D(2, 3, 32, 64);
auto attrs = makeMinimalAttrs(q, k, v);
const GraphAttributes graphAttrs;
const SdpaFwdNode node(std::move(attrs), graphAttrs);
auto err = node.pre_validate_node();
EXPECT_EQ(err.code, error_code_t::INVALID_VALUE);
}
TEST(TestSdpaFwdNode, PreValidateSucceedsWithAttnMask)
{
auto q = makeTensor4D(2, 8, 16, 64);
auto k = makeTensor4D(2, 8, 32, 64);
auto v = makeTensor4D(2, 8, 32, 64);
auto attrs = makeMinimalAttrs(q, k, v);
// Valid mask: [2, 8, 16, 32] — exact match
auto mask = std::make_shared<TensorAttributes>();
mask->set_dim({2, 8, 16, 32});
attrs.set_bias(mask);
const GraphAttributes graphAttrs;
const SdpaFwdNode node(std::move(attrs), graphAttrs);
auto err = node.pre_validate_node();
EXPECT_EQ(err.code, error_code_t::OK) << err.err_msg;
}
TEST(TestSdpaFwdNode, PreValidateSucceedsWithBroadcastAttnMask)
{
auto q = makeTensor4D(2, 8, 16, 64);
auto k = makeTensor4D(2, 8, 32, 64);
auto v = makeTensor4D(2, 8, 32, 64);
auto attrs = makeMinimalAttrs(q, k, v);
// Broadcast mask: last two dims are 1
auto mask = std::make_shared<TensorAttributes>();
mask->set_dim({1, 1});
attrs.set_bias(mask);
const GraphAttributes graphAttrs;
const SdpaFwdNode node(std::move(attrs), graphAttrs);
auto err = node.pre_validate_node();
EXPECT_EQ(err.code, error_code_t::OK) << err.err_msg;
}
TEST(TestSdpaFwdNode, PreValidateFailsAttnMaskRankTooLarge)
{
auto q = makeTensor4D(2, 8, 16, 64);
auto k = makeTensor4D(2, 8, 32, 64);
auto v = makeTensor4D(2, 8, 32, 64);
auto attrs = makeMinimalAttrs(q, k, v);
// Rank-5 mask is invalid
auto mask = std::make_shared<TensorAttributes>();
mask->set_dim({1, 2, 8, 16, 32});
attrs.set_bias(mask);
const GraphAttributes graphAttrs;
const SdpaFwdNode node(std::move(attrs), graphAttrs);
auto err = node.pre_validate_node();
EXPECT_EQ(err.code, error_code_t::INVALID_VALUE);
}
TEST(TestSdpaFwdNode, PreValidateFailsAttnScaleTensorAndValueBothSet)
{
auto q = makeTensor4D(2, 8, 16, 64);
auto k = makeTensor4D(2, 8, 32, 64);
auto v = makeTensor4D(2, 8, 32, 64);
auto attrs = makeMinimalAttrs(q, k, v);
// Rule 6: attn_scale tensor and attn_scale_value are mutually exclusive.
auto scale = std::make_shared<TensorAttributes>();
scale->set_dim({1}).set_is_pass_by_value(true);
attrs.set_attn_scale(scale);
attrs.attn_scale_value = 1.0f;
const GraphAttributes graphAttrs;
const SdpaFwdNode node(std::move(attrs), graphAttrs);
auto err = node.pre_validate_node();
EXPECT_EQ(err.code, error_code_t::INVALID_VALUE);
}
TEST(TestSdpaFwdNode, InferPropertiesSetsOutputShape)
{
// headDimV=64, so O shape should be [2, 8, 16, 64]
auto q = makeTensor4D(2, 8, 16, 64);
auto k = makeTensor4D(2, 8, 32, 64);
auto v = makeTensor4D(2, 8, 32, 64);
auto attrs = makeMinimalAttrs(q, k, v);
auto o = attrs.get_o();
const GraphAttributes graphAttrs;
SdpaFwdNode node(std::move(attrs), graphAttrs);
auto err = node.infer_properties_node();
EXPECT_EQ(err.code, error_code_t::OK) << err.err_msg;
auto dims = o->get_dim();
ASSERT_EQ(dims.size(), 4u);
EXPECT_EQ(dims[0], 2);
EXPECT_EQ(dims[1], 8);
EXPECT_EQ(dims[2], 16);
EXPECT_EQ(dims[3], 64); // headDimV
}
TEST(TestSdpaFwdNode, InferPropertiesSetsOutputShapeDifferentHeadDimV)
{
// headDimV=32 (different from headDimQK=64), O shape should be [2, 8, 16, 32]
auto q = makeTensor4D(2, 8, 16, 64);
auto k = makeTensor4D(2, 8, 32, 64);
auto v = makeTensor4D(2, 8, 32, 32); // headDimV=32
auto attrs = makeMinimalAttrs(q, k, v);
auto o = attrs.get_o();
const GraphAttributes graphAttrs;
SdpaFwdNode node(std::move(attrs), graphAttrs);
auto err = node.infer_properties_node();
EXPECT_EQ(err.code, error_code_t::OK) << err.err_msg;
auto dims = o->get_dim();
ASSERT_EQ(dims.size(), 4u);
EXPECT_EQ(dims[0], 2);
EXPECT_EQ(dims[1], 8);
EXPECT_EQ(dims[2], 16);
EXPECT_EQ(dims[3], 32); // headDimV, not headDimQK
}
TEST(TestSdpaFwdNode, InferPropertiesSetsStatsShape)
{
auto q = makeTensor4D(2, 8, 16, 64);
auto k = makeTensor4D(2, 8, 32, 64);
auto v = makeTensor4D(2, 8, 32, 64);
auto attrs = makeMinimalAttrs(q, k, v);
auto stats = std::make_shared<TensorAttributes>();
attrs.set_stats(stats);
const GraphAttributes graphAttrs;
SdpaFwdNode node(std::move(attrs), graphAttrs);
auto err = node.infer_properties_node();
EXPECT_EQ(err.code, error_code_t::OK) << err.err_msg;
// Stats shape: [batch, num_heads, seq_q, 1]
auto dims = stats->get_dim();
ASSERT_EQ(dims.size(), 4u);
EXPECT_EQ(dims[0], 2);
EXPECT_EQ(dims[1], 8);
EXPECT_EQ(dims[2], 16);
EXPECT_EQ(dims[3], 1);
// Stats strides should be set
EXPECT_FALSE(stats->get_stride().empty());
}
TEST(TestSdpaFwdNode, InferPropertiesSetsOutputStrides)
{
auto q = makeTensor4D(2, 8, 16, 64);
auto k = makeTensor4D(2, 8, 32, 64);
auto v = makeTensor4D(2, 8, 32, 64);
auto attrs = makeMinimalAttrs(q, k, v);
auto o = attrs.get_o();
const GraphAttributes graphAttrs;
SdpaFwdNode node(std::move(attrs), graphAttrs);
auto err = node.infer_properties_node();
EXPECT_EQ(err.code, error_code_t::OK) << err.err_msg;
auto strides = o->get_stride();
ASSERT_EQ(strides.size(), 4u);
// Row-major strides for [2, 8, 16, 64]: [8*16*64, 16*64, 64, 1]
EXPECT_EQ(strides[3], 1);
EXPECT_EQ(strides[2], 64);
EXPECT_EQ(strides[1], 16 * 64);
EXPECT_EQ(strides[0], 8 * 16 * 64);
}
TEST(TestSdpaFwdNode, InferPropertiesPreservesExplicitOutputShape)
{
// If O dims are already set, they should not be overwritten
auto q = makeTensor4D(2, 8, 16, 64);
auto k = makeTensor4D(2, 8, 32, 64);
auto v = makeTensor4D(2, 8, 32, 64);
SdpaAttributes attrs;
attrs.set_q(q);
attrs.set_k(k);
attrs.set_v(v);
auto o = std::make_shared<TensorAttributes>();
o->set_dim({2, 8, 16, 64});
o->set_stride({8192, 1024, 64, 1});
attrs.set_o(o);
const GraphAttributes graphAttrs;
SdpaFwdNode node(std::move(attrs), graphAttrs);
auto err = node.infer_properties_node();
EXPECT_EQ(err.code, error_code_t::OK) << err.err_msg;
// Dims should remain unchanged
EXPECT_EQ(o->get_dim(), (std::vector<int64_t>{2, 8, 16, 64}));
// Strides should remain unchanged (they were already set)
EXPECT_EQ(o->get_stride(), (std::vector<int64_t>{8192, 1024, 64, 1}));
}
TEST(TestSdpaFwdNode, PreValidateFailsBatchMismatchQK)
{
// K batch=4 differs from Q batch=2
auto q = makeTensor4D(2, 8, 16, 64);
auto k = makeTensor4D(4, 8, 32, 64);
auto v = makeTensor4D(2, 8, 32, 64);
auto attrs = makeMinimalAttrs(q, k, v);
const GraphAttributes graphAttrs;
const SdpaFwdNode node(std::move(attrs), graphAttrs);
auto err = node.pre_validate_node();
EXPECT_EQ(err.code, error_code_t::INVALID_VALUE);
}
TEST(TestSdpaFwdNode, PreValidateFailsBatchMismatchQV)
{
// V batch=4 differs from Q batch=2
auto q = makeTensor4D(2, 8, 16, 64);
auto k = makeTensor4D(2, 8, 32, 64);
auto v = makeTensor4D(4, 8, 32, 64);
auto attrs = makeMinimalAttrs(q, k, v);
const GraphAttributes graphAttrs;
const SdpaFwdNode node(std::move(attrs), graphAttrs);
auto err = node.pre_validate_node();
EXPECT_EQ(err.code, error_code_t::INVALID_VALUE);
}
TEST(TestSdpaFwdNode, GetNodeTypeReturnsSdpaFwd)
{
const GraphAttributes graphAttrs;
const SdpaFwdNode node(SdpaAttributes{}, graphAttrs);
EXPECT_EQ(node.getNodeType(), NodeType::SDPA_FWD);
}
//==============================================================================
// Fail-loudly drain of unsupported cuDNN-compat setters
//==============================================================================
TEST(TestSdpaFwdNode, PreValidateFailsUnsupportedScoreMod)
{
auto q = makeTensor4D(2, 8, 16, 64);
auto k = makeTensor4D(2, 8, 32, 64);
auto v = makeTensor4D(2, 8, 32, 64);
auto attrs = makeMinimalAttrs(q, k, v);
attrs.set_score_mod([](int) { return 0; });
const GraphAttributes graphAttrs;
const SdpaFwdNode node(std::move(attrs), graphAttrs);
auto err = node.pre_validate_node();
EXPECT_EQ(err.code, error_code_t::INVALID_VALUE);
EXPECT_NE(err.err_msg.find("score modifier"), std::string::npos) << err.err_msg;
}
TEST(TestSdpaFwdNode, PreValidateDrainRunsBeforeShapeChecks)
{
// Q is rank-3 (shape-invalid) AND an unsupported setter is present. The drain
// must run first, so the surfaced message is the unsupported one.
auto q = std::make_shared<TensorAttributes>();
q->set_dim({2, 8, 16});
auto k = makeTensor4D(2, 8, 32, 64);
auto v = makeTensor4D(2, 8, 32, 64);
auto attrs = makeMinimalAttrs(q, k, v);
attrs.set_score_mod([](int) { return 0; });
const GraphAttributes graphAttrs;
const SdpaFwdNode node(std::move(attrs), graphAttrs);
auto err = node.pre_validate_node();
EXPECT_EQ(err.code, error_code_t::INVALID_VALUE);
EXPECT_NE(err.err_msg.find("score modifier"), std::string::npos) << err.err_msg;
EXPECT_EQ(err.err_msg.find("rank"), std::string::npos) << err.err_msg;
}