Files
TheRock/rocm-libraries/projects/miopen/test/gtest/lstm_large_workspace.cpp
T
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

238 lines
9.9 KiB
C++

// Copyright © Advanced Micro Devices, Inc., or its affiliates.
// SPDX-License-Identifier: MIT
// Regression test: workspace tensor element count exceeds INT_MAX for fp16 LSTM.
#include <gtest/gtest.h>
#include <miopen/miopen.h>
#include <hip/hip_runtime.h>
#include <bit>
#include <climits>
#include <cstddef>
#include <cstdint>
#include <vector>
#include "get_handle.hpp"
#include "gtest_desc_guard.hpp"
#include "../workspace.hpp"
namespace {
// Smallest config that pushes fp16 workspace element count past INT_MAX (~5.5e9 elts).
struct LargeRnnConfig
{
int batch_per_step = 1000;
int seq_len = 600;
int input_size = 60;
int hidden_size = 128;
int num_layers = 4;
miopenRNNDirectionMode_t dir = miopenRNNbidirection;
miopenRNNMode_t mode = miopenLSTM;
miopenRNNInputMode_t input_mode = miopenRNNlinear;
miopenRNNBiasMode_t bias_mode = miopenRNNwithBias;
miopenRNNAlgo_t algo = miopenRNNdefault;
miopenDataType_t dtype = miopenHalf;
};
void MakeXDescs(const LargeRnnConfig& cfg, TensorDescVecGuard& xDescs)
{
xDescs.descs.assign(cfg.seq_len, nullptr);
for(int i = 0; i < cfg.seq_len; ++i)
{
ASSERT_EQ(miopenCreateTensorDescriptor(&xDescs[i]), miopenStatusSuccess);
std::array<int, 2> lens = {cfg.batch_per_step, cfg.input_size};
ASSERT_EQ(miopenSetTensorDescriptor(xDescs[i], cfg.dtype, 2, lens.data(), nullptr),
miopenStatusSuccess);
}
}
} // namespace
// Parameterized only so that INSTANTIATE_TEST_SUITE_P can put these tests under the
// "Standard/" prefix used by test_categories.yaml; the int param itself is unused.
struct GPU_LSTM_LargeWorkspace_FP16 : public ::testing::TestWithParam<int>
{
};
// Tripwire: if a future MIOpen change shrinks the workspace below INT_MAX bytes for this
// config, this assertion fires and someone needs to choose a larger LargeRnnConfig.
TEST_P(GPU_LSTM_LargeWorkspace_FP16, GetWorkspaceSizeOverflowsInt)
{
auto&& handle = get_handle();
LargeRnnConfig cfg;
RNNDescGuard rnn;
ASSERT_EQ(rnn.getStatus(), miopenStatusSuccess);
// Free the internal DropoutDescriptor that miopenCreateRNNDescriptor allocated;
// miopenSetRNNDescriptor below will overwrite the field and otherwise leak it.
DestroyInternalRnnDropoutDesc(rnn);
ASSERT_EQ(miopenSetRNNDescriptor(rnn,
cfg.hidden_size,
cfg.num_layers,
cfg.input_mode,
cfg.dir,
cfg.mode,
cfg.bias_mode,
cfg.algo,
cfg.dtype),
miopenStatusSuccess);
TensorDescVecGuard xDescs;
MakeXDescs(cfg, xDescs);
size_t ws_size = 0;
auto status = miopenGetRNNWorkspaceSize(&handle, rnn, cfg.seq_len, xDescs.data(), &ws_size);
EXPECT_EQ(status, miopenStatusSuccess);
EXPECT_GT(ws_size, static_cast<size_t>(INT_MAX));
// miopenDestroyRNNDescriptor does not free the internal DropoutDescriptor that
// miopenSetRNNDescriptor (8-arg form, no user-owned dropout) allocated.
DestroyInternalRnnDropoutDesc(rnn);
}
TEST_P(GPU_LSTM_LargeWorkspace_FP16, ForwardInferenceSucceedsWhenWorkspaceExceedsInt)
{
auto&& handle = get_handle();
LargeRnnConfig cfg;
RNNDescGuard rnn;
ASSERT_EQ(rnn.getStatus(), miopenStatusSuccess);
// Free the internal DropoutDescriptor that miopenCreateRNNDescriptor allocated;
// miopenSetRNNDescriptor below will overwrite the field and otherwise leak it.
DestroyInternalRnnDropoutDesc(rnn);
ASSERT_EQ(miopenSetRNNDescriptor(rnn,
cfg.hidden_size,
cfg.num_layers,
cfg.input_mode,
cfg.dir,
cfg.mode,
cfg.bias_mode,
cfg.algo,
cfg.dtype),
miopenStatusSuccess);
TensorDescVecGuard xDescs;
TensorDescVecGuard yDescs;
MakeXDescs(cfg, xDescs);
const int directions = (cfg.dir == miopenRNNbidirection) ? 2 : 1;
const int y_vec = cfg.hidden_size * directions;
yDescs.descs.assign(cfg.seq_len, nullptr);
for(int i = 0; i < cfg.seq_len; ++i)
{
ASSERT_EQ(miopenCreateTensorDescriptor(&yDescs[i]), miopenStatusSuccess);
std::array<int, 2> lens = {cfg.batch_per_step, y_vec};
ASSERT_EQ(miopenSetTensorDescriptor(yDescs[i], cfg.dtype, 2, lens.data(), nullptr),
miopenStatusSuccess);
}
TensorDescGuard hxDesc;
ASSERT_EQ(hxDesc.getStatus(), miopenStatusSuccess);
{
std::array<int, 3> lens = {
cfg.num_layers * directions, cfg.batch_per_step, cfg.hidden_size};
ASSERT_EQ(miopenSetTensorDescriptor(hxDesc, cfg.dtype, 3, lens.data(), nullptr),
miopenStatusSuccess);
}
size_t ws_size = 0;
ASSERT_EQ(miopenGetRNNWorkspaceSize(&handle, rnn, cfg.seq_len, xDescs.data(), &ws_size),
miopenStatusSuccess);
ASSERT_GT(ws_size, static_cast<size_t>(INT_MAX));
size_t w_size = 0;
ASSERT_EQ(miopenGetRNNParamsSize(&handle, rnn, xDescs[0], &w_size, cfg.dtype),
miopenStatusSuccess);
TensorDescGuard wDesc;
ASSERT_EQ(wDesc.getStatus(), miopenStatusSuccess);
ASSERT_EQ(miopenGetRNNParamsDescriptor(&handle, rnn, xDescs[0], wDesc, cfg.dtype),
miopenStatusSuccess);
// Use the official MIOpen sizing APIs so that buffer sizes account for all internal
// alignment / padding requirements.
std::size_t x_bytes = 0;
std::size_t y_bytes = 0;
std::size_t h_bytes = 0;
ASSERT_EQ(miopenGetRNNInputTensorSize(&handle, rnn, cfg.seq_len, xDescs.data(), &x_bytes),
miopenStatusSuccess);
ASSERT_EQ(miopenGetRNNInputTensorSize(&handle, rnn, cfg.seq_len, yDescs.data(), &y_bytes),
miopenStatusSuccess);
ASSERT_EQ(miopenGetRNNHiddenTensorSize(&handle, rnn, cfg.seq_len, xDescs.data(), &h_bytes),
miopenStatusSuccess);
// Add headroom over the raw tensor sum for runtime/library reservations,
// allocator fragmentation, and (on consumer cards) the display compositor.
// Use max(+1 GiB, +10%) to cover both the absolute and the proportional
// components, then round up to the next power of two.
const std::size_t raw_mem = ws_size + x_bytes + y_bytes + w_size + 4 * h_bytes;
const std::size_t headroom = std::max<std::size_t>(1ULL << 30, raw_mem / 10);
const std::size_t required_mem = std::bit_ceil(raw_mem + headroom);
const std::size_t device_mem = handle.GetGlobalMemorySize();
if(device_mem < required_mem)
{
GTEST_SKIP() << "Insufficient device memory: need " << required_mem
<< " bytes (rounded up to next power of 2), device has " << device_mem;
}
Workspace x_buf{x_bytes};
Workspace y_buf{y_bytes};
Workspace w_buf{w_size};
Workspace hx_buf{h_bytes};
Workspace cx_buf{h_bytes};
Workspace hy_buf{h_bytes};
Workspace cy_buf{h_bytes};
Workspace ws_buf{ws_size};
// Zero weights, hidden state, and inputs so the analytic LSTM output is exactly zero
// (sigmoid(0)*tanh(0) = 0). Pre-fill y with a sentinel so we can detect any later
// "silent" CopyTensor failure that leaves output regions unwritten.
ASSERT_EQ(hipMemset(w_buf.ptr(), 0, w_size), hipSuccess);
ASSERT_EQ(hipMemset(hx_buf.ptr(), 0, h_bytes), hipSuccess);
ASSERT_EQ(hipMemset(cx_buf.ptr(), 0, h_bytes), hipSuccess);
ASSERT_EQ(hipMemset(x_buf.ptr(), 0, x_bytes), hipSuccess);
ASSERT_EQ(hipMemset(y_buf.ptr(), 0xFF, y_bytes), hipSuccess);
auto status = miopenRNNForwardInference(&handle,
rnn,
cfg.seq_len,
xDescs.data(),
x_buf.ptr(),
hxDesc,
hx_buf.ptr(),
hxDesc,
cx_buf.ptr(),
wDesc,
w_buf.ptr(),
yDescs.data(),
y_buf.ptr(),
hxDesc,
hy_buf.ptr(),
hxDesc,
cy_buf.ptr(),
ws_buf.ptr(),
ws_size);
ASSERT_EQ(status, miopenStatusSuccess);
// With zero inputs/weights/biases/hidden state, every fp16 output element should be
// exactly +0.0. Any nonzero byte indicates either silent corruption (e.g. a truncated
// CopyTensor offset writing stale workspace bytes into y) or an unwritten region
// still holding the 0xFF sentinel.
std::vector<std::uint16_t> y_host(y_bytes / sizeof(std::uint16_t));
ASSERT_EQ(hipMemcpy(y_host.data(), y_buf.ptr(), y_bytes, hipMemcpyDeviceToHost), hipSuccess);
for(size_t i = 0; i < y_host.size(); ++i)
{
ASSERT_EQ(y_host[i], 0u) << "Nonzero fp16 bits at element " << i << "/" << y_host.size()
<< " (offset " << i * sizeof(std::uint16_t) << " bytes)";
}
// miopenDestroyRNNDescriptor does not free the internal DropoutDescriptor that
// miopenSetRNNDescriptor (8-arg form, no user-owned dropout) allocated.
DestroyInternalRnnDropoutDesc(rnn);
}
INSTANTIATE_TEST_SUITE_P(Standard, GPU_LSTM_LargeWorkspace_FP16, testing::Values(0));