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

275 lines
12 KiB
C++

// Copyright © Advanced Micro Devices, Inc., or its affiliates.
// SPDX-License-Identifier: MIT
#include <array>
#include <cstddef>
#include <cstdint>
#include <string>
#include <hipblaslt/hipblaslt.h>
#include <hipdnn_data_sdk/utilities/ScopedResource.hpp>
#include <hipdnn_flatbuffers_sdk/utilities/FlatbufferUtils.hpp>
#include <hipdnn_plugin_sdk/PluginException.hpp>
#include "HipblasltMxMatmulPlan.hpp"
#include "HipblasltUtils.hpp"
#include "HipdnnEnginePluginHandle.hpp"
namespace hipblaslt_plugin
{
namespace
{
// Infer the hipBLASLt transpose op from a tensor's strides: row-major
// (stride[-1]==1) → HIPBLAS_OP_N; column-major (stride[-2]==1) → HIPBLAS_OP_T.
// Mirrors MatmulParams::getTrans for the plain matmul plan.
hipblasOperation_t getTransFromStrides(
const hipdnn_flatbuffers_sdk::flatbuffer_utilities::TensorAttributesWrapper& t)
{
const auto& strides = t.strides();
PLUGIN_THROW_IF_FALSE(strides.size() > 1,
HIPDNN_PLUGIN_STATUS_BAD_PARAM,
"Unsupported stride for MX input matrix: " + t.name());
if(strides[strides.size() - 1] == 1)
{
return HIPBLAS_OP_N;
}
if(strides[strides.size() - 2] == 1)
{
return HIPBLAS_OP_T;
}
throw hipdnn_plugin_sdk::HipdnnPluginException(
HIPDNN_PLUGIN_STATUS_BAD_PARAM, "Unsupported stride for MX input matrix: " + t.name());
}
// Matches hipMalloc's 256-byte alignment guarantee, so the matmul workspace
// following the reserved scale region stays as aligned as a fresh allocation.
constexpr size_t WORKSPACE_ALIGNMENT = 256;
size_t alignUp(size_t value, size_t alignment)
{
return ((value + alignment - 1) / alignment) * alignment;
}
} // namespace
MxMatmulParams::MxMatmulParams(
const hipdnn_flatbuffers_sdk::data_objects::BlockScaleDequantizeAttributes& deqAttrA,
const hipdnn_flatbuffers_sdk::data_objects::BlockScaleDequantizeAttributes& deqAttrB,
const hipdnn_flatbuffers_sdk::data_objects::MatmulAttributes& matmulAttr,
const std::unordered_map<int64_t,
const hipdnn_flatbuffers_sdk::data_objects::TensorAttributes*>&
tensorMap)
{
// tXA/tXB are the graph's logical A/B (the block-scaled X inputs of dequant A/B).
const auto tXA = hipblaslt_utils::findTensorAttributes(tensorMap, deqAttrA.x_tensor_uid());
const auto tXB = hipblaslt_utils::findTensorAttributes(tensorMap, deqAttrB.x_tensor_uid());
const auto tC = hipblaslt_utils::findTensorAttributes(tensorMap, matmulAttr.c_tensor_uid());
// Row-major BLAS trick: hipDNN tensors are row-major but hipBLASLt is
// column-major, so we compute C^T = B^T * A^T by presenting our B as
// hipBLAS's A operand and our A as its B operand. The swap is applied once,
// here, so every member is already in hipBLAS's frame and execute() needs no
// further swapping: a()/aScaleUid() are what hipBLAS receives as A/A_SCALE.
_matrixLayoutA = HipblasltMatrixLayout(tXB); // hipBLAS A <- our B
_matrixLayoutB = HipblasltMatrixLayout(tXA); // hipBLAS B <- our A
_matrixLayoutC = HipblasltMatrixLayout(tC);
_aScaleUid = deqAttrB.scale_tensor_uid(); // hipBLAS A_SCALE <- our B's scale
_bScaleUid = deqAttrA.scale_tensor_uid(); // hipBLAS B_SCALE <- our A's scale
// Our A is [..., M, K], scales blocked 32-wide along K (innermost). Our A's
// scale becomes hipBLAS's B_SCALE and is transposed [M, K/32] -> [K/32, M] at
// execute time; M and the K-block count drive that transpose.
const auto& aDims = tXA.dims();
_m = aDims[aDims.size() - 2];
_kBlocks = aDims[aDims.size() - 1] / VEC32_BLOCK_SIZE;
// FP8 OCP MX GEMM always uses HIPBLAS_COMPUTE_32F. desc transA/transB are in
// hipBLAS's frame too: transA = getTrans(hipBLAS A = our B), transB = our A.
_matmulDesc = HipblasltMatmulDesc(
getTransFromStrides(tXB), getTransFromStrides(tXA), HIPBLAS_COMPUTE_32F, HIP_R_32F);
_matmulDesc.setAScaleMode(HIPBLASLT_MATMUL_MATRIX_SCALE_VEC32_UE8M0);
_matmulDesc.setBScaleMode(HIPBLASLT_MATMUL_MATRIX_SCALE_VEC32_UE8M0);
}
const HipblasltMatrixLayout& MxMatmulParams::a() const
{
return _matrixLayoutA;
}
const HipblasltMatrixLayout& MxMatmulParams::b() const
{
return _matrixLayoutB;
}
const HipblasltMatrixLayout& MxMatmulParams::c() const
{
return _matrixLayoutC;
}
const HipblasltMatmulDesc& MxMatmulParams::desc() const
{
return _matmulDesc;
}
int64_t MxMatmulParams::aScaleUid() const
{
return _aScaleUid;
}
int64_t MxMatmulParams::bScaleUid() const
{
return _bScaleUid;
}
int64_t MxMatmulParams::m() const
{
return _m;
}
int64_t MxMatmulParams::kBlocks() const
{
return _kBlocks;
}
MxMatmulPlan::MxMatmulPlan(const HipdnnEnginePluginHandle& handle, MxMatmulParams&& params)
: _params(std::move(params))
{
// Same max workspace approach as MatmulPlan: 128 MB to allow hipBLASLt to
// find the most performant MX GEMM algorithm.
auto maxWorkspaceSize = static_cast<size_t>(128 * 1024 * 1024); // 128 MB
hipblasLtMatmulPreference_t prefHandle;
THROW_ON_HIPBLASLT_FAILURE(hipblasLtMatmulPreferenceCreate(&prefHandle));
// Own the preference via RAII.
hipdnn_data_sdk::utilities::ScopedResource<hipblasLtMatmulPreference_t> const pref(
prefHandle, [](hipblasLtMatmulPreference_t p) {
LOG_ON_HIPBLASLT_FAILURE(hipblasLtMatmulPreferenceDestroy(p));
});
THROW_ON_HIPBLASLT_FAILURE(
hipblasLtMatmulPreferenceSetAttribute(pref.get(),
HIPBLASLT_MATMUL_PREF_MAX_WORKSPACE_BYTES,
&maxWorkspaceSize,
sizeof(maxWorkspaceSize)));
// Operands are already in hipBLAS's frame (swapped in MxMatmulParams), so
// a()/b() are passed straight through as hipBLAS A/B.
constexpr int REQUEST_SOLUTIONS = 1;
std::array<hipblasLtMatmulHeuristicResult_t, REQUEST_SOLUTIONS> heuristicResult{};
int returnedAlgoCount = 0;
THROW_ON_HIPBLASLT_FAILURE(hipblasLtMatmulAlgoGetHeuristic(handle.hipblasltHandle,
_params.desc().matmulDesc(),
_params.a().matrixLayout(),
_params.b().matrixLayout(),
_params.c().matrixLayout(),
_params.c().matrixLayout(),
pref.get(),
REQUEST_SOLUTIONS,
heuristicResult.data(),
&returnedAlgoCount));
PLUGIN_THROW_IF_FALSE(returnedAlgoCount > 0,
HIPDNN_PLUGIN_STATUS_INTERNAL_ERROR,
"hipBLASLt has not found MX GEMM algorithm!");
_heuristicResult = heuristicResult[0];
_workspaceSize = _heuristicResult.workspaceSize;
// Our A's scale (hipBLAS B_SCALE) is transposed on-device at execute time;
// reserve aligned room for it at the front of the workspace so the plan owns
// no device memory itself.
const auto scaleBytes = static_cast<size_t>(_params.m() * _params.kBlocks());
_scaleBufferBytes = alignUp(scaleBytes, WORKSPACE_ALIGNMENT);
// Prebuild the transpose descriptors; reused on every execute. Our A's scale
// [M, K/32] row-major is viewed column-major as (K/32, M) on input and written
// as (M, K/32) column-major == [K/32, M] row-major on output. R_8I moves the
// UE8M0 bytes verbatim without interpreting them numerically.
const auto m = static_cast<uint64_t>(_params.m());
const auto kBlocks = static_cast<uint64_t>(_params.kBlocks());
_scaleTransposeDesc = HipblasltMatrixTransformDesc(HIP_R_32F, HIPBLAS_OP_T);
_scaleSrcLayout = HipblasltMatrixLayout(HIP_R_8I, kBlocks, m, static_cast<int64_t>(kBlocks));
_scaleDstLayout = HipblasltMatrixLayout(HIP_R_8I, m, kBlocks, static_cast<int64_t>(m));
}
size_t MxMatmulPlan::getWorkspaceSize([[maybe_unused]] const HipdnnEnginePluginHandle& handle) const
{
return _scaleBufferBytes + _workspaceSize;
}
void MxMatmulPlan::execute(const HipdnnEnginePluginHandle& handle,
const hipdnnPluginDeviceBuffer_t* deviceBuffers,
uint32_t numDeviceBuffers,
void* workspace) const
{
// getWorkspaceSize() always reports a non-zero size for MX GEMM (the scale
// transpose region is reserved unconditionally), so a null workspace here is
// a caller contract violation. Validate before dereferencing it below.
PLUGIN_THROW_IF_TRUE(workspace == nullptr,
HIPDNN_PLUGIN_STATUS_BAD_PARAM,
"MxMatmulPlan::execute: workspace is null but MX GEMM requires a "
"non-zero workspace (see getWorkspaceSize())");
auto aBuffer
= hipblaslt_utils::findDeviceBuffer(_params.a().uid(), deviceBuffers, numDeviceBuffers);
auto bBuffer
= hipblaslt_utils::findDeviceBuffer(_params.b().uid(), deviceBuffers, numDeviceBuffers);
auto cBuffer
= hipblaslt_utils::findDeviceBuffer(_params.c().uid(), deviceBuffers, numDeviceBuffers);
auto aScaleBuffer
= hipblaslt_utils::findDeviceBuffer(_params.aScaleUid(), deviceBuffers, numDeviceBuffers);
auto bScaleBuffer
= hipblaslt_utils::findDeviceBuffer(_params.bScaleUid(), deviceBuffers, numDeviceBuffers);
// Workspace layout: [ transposed B_SCALE | hipBLASLt matmul workspace ].
void* transposedBScale = workspace;
void* matmulWorkspace = static_cast<void*>(static_cast<char*>(workspace) + _scaleBufferBytes);
// hipBLASLt expects A_SCALE as [K/32, m] and B_SCALE as [K/32, n]. A_SCALE
// (our B's scale) is already in that layout. B_SCALE (our A's scale) is
// [M, K/32] and must be physically transposed to [K/32, M], because scale
// pointers carry no layout handle to swap for free.
THROW_ON_HIPBLASLT_FAILURE(hipblasLtMatrixTransform(handle.hipblasltHandle,
_scaleTransposeDesc.transformDesc(),
&ALPHA,
bScaleBuffer.ptr,
_scaleSrcLayout.matrixLayout(),
&BETA,
nullptr,
nullptr,
transposedBScale,
_scaleDstLayout.matrixLayout(),
handle.getStream()));
// Clone the plan's descriptor so this call owns its own copy: scale pointers
// are per-call device addresses, and mutating a shared descriptor would make
// concurrent execute() calls on one plan instance race. Already in hipBLAS's
// frame (swapped in MxMatmulParams).
HipblasltMatmulDesc desc = _params.desc().clone();
desc.setAScalePointer(aScaleBuffer.ptr);
desc.setBScalePointer(transposedBScale);
// Operands are already in hipBLAS's frame (swapped in MxMatmulParams), so
// pass them straight through.
THROW_ON_HIPBLASLT_FAILURE(hipblasLtMatmul(handle.hipblasltHandle,
desc.matmulDesc(),
&ALPHA,
aBuffer.ptr,
_params.a().matrixLayout(),
bBuffer.ptr,
_params.b().matrixLayout(),
&BETA,
cBuffer.ptr,
_params.c().matrixLayout(),
cBuffer.ptr,
_params.c().matrixLayout(),
&_heuristicResult.algo,
matmulWorkspace,
_workspaceSize,
handle.getStream()));
}
} // namespace hipblaslt_plugin