mirror of
https://github.com/ROCm/composable_kernel.git
synced 2026-06-29 19:28:33 +00:00
[ck] Enforce ASCII-only C/C++ sources for hipRTC compatibility (#7829) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary CK source files must be compilable via **hipRTC (HIP runtime compilation)**, whose preprocessor does not accept non-ASCII bytes anywhere in a translation unit — **including in comments**. Bytes that are harmless under `hipcc` (em-dashes, smart quotes, multiplication signs, Greek letters, box-drawing glyphs, etc.) cause hipRTC to fail at preprocessing time. These regularly leak in via LLM-assisted authoring or copy/paste from formatted documents and silently break hipRTC paths that are not exercised by the default `hipcc`-based build matrix. This PR (a) cleans every existing violation (53 files) and (b) adds a pre-checkin gate so new violations are rejected before merge. ## File extensions covered Both the cleanup scan and the new Jenkins enforcement stage use the same predicate: ``` *.h *.hpp *.cpp *.h.in *.hpp.in *.cpp.in *.inc *.cl ``` (excluding `*/build/*` and `*/include/rapidjson/*`). This is a strict superset of the existing `Clang Format` stage's predicate — `*.inc` is added so test-fixture include files are also gated. The local pre-commit hook's `c++/inc` type filter covers the same set. ## Why no enforcement today CK is opted out of the rocm-libraries root `.pre-commit-config.yaml`, so the existing `pre-commit` workflow doesn't touch CK. The local CK `.pre-commit-config.yaml` only runs for developers who installed hooks. The **authoritative gate is therefore the new Jenkins stage** in this PR; the local hook is convenience. ## Commit layout (bisect-friendly) 1. `79798aa6261` — **`[ck] Convert reflect/ rendering to ASCII for hipRTC compatibility`** Behavior change, isolated. `TreeFormatter` swaps `├─ / └─ / │ ` for `|- / +- / | ` (3-col width preserved so alignment is unchanged). `conv_description.hpp` swaps `×` for `x` as the dimension separator. `test_conv_description.cpp` expected strings updated in lockstep so the snapshot test stays green. This is the only commit in the series with observable runtime impact. 2. `738fdb0d81c` — **`[ck] Strip non-ASCII bytes from C++ sources for hipRTC compatibility`** Mechanical text cleanup across 53 files. Replacements happen in comments or in `std::cout` strings that are not asserted on by any test. None of the 174 `.inc` files in the tree required edits, but they were in the scan's predicate so the enforcement stage's predicate is a superset of what was scanned. Full replacement table in the commit message. 3. `1d7cd8ba235` — **`[ck] Enforce ASCII-only C/C++ sources for hipRTC compatibility`** - New `projects/composablekernel/script/check_ascii_only.sh` (modeled on `check_copyright_year.sh`). - New entry in `projects/composablekernel/.pre-commit-config.yaml` under the local-hooks block (`types_or: [c++, inc]`). - New `ASCII Only Check` parallel stage in `projects/composablekernel/Jenkinsfile`'s `Static checks` block, mirroring the existing `Clang Format` stage but with `*.inc` added to the find predicate. Always-on, no `RUN_CPPCHECK` gate. The tree is buildable at every commit boundary. Commit 1 leaves 50 known violations; commit 2 leaves 0; commit 3 wires the gate. ## Demo Script output on a synthesized violation: ``` $ printf '// em-dash test \xe2\x80\x94 here\n' > /tmp/bad.cpp $ projects/composablekernel/script/check_ascii_only.sh /tmp/bad.cpp ERROR: /tmp/bad.cpp contains non-ASCII bytes: 1:// em-dash test — here Fix: replace with ASCII (em-dash -> --, smart quotes -> ", arrows -> ->, etc.) $ echo $? 1 ``` Full repo scan after the cleanup commits (note the `-name '*.inc'` clause): ``` $ cd projects/composablekernel && find . -type f \( -name '*.h' -o -name '*.hpp' -o -name '*.cpp' \ -o -name '*.h.in' -o -name '*.hpp.in' -o -name '*.cpp.in' -o -name '*.inc' -o -name '*.cl' \) \ -not -path '*/build/*' -not -path '*/include/rapidjson/*' -print0 \ | xargs -0 -P 8 -n 64 script/check_ascii_only.sh $ echo $? 0 ``` ## Test plan - [ ] Jenkins PR build: confirm new `Static checks -> ASCII Only Check` stage runs green over the full predicate (incl. `*.inc`) and existing `Clang Format` stage is unaffected. - [ ] `test_conv_description` passes against the ASCII tree-formatter output (touched in commit 1). - [ ] Local: `pre-commit run ascii-only-checker --all-files` runs cleanly after installing CK pre-commit hooks via `script/install_precommit.sh`. - [ ] Manually inject a non-ASCII byte in any `.cpp/.hpp/.inc` file, push: confirm Jenkins fails the new stage with a clear error. - [ ] Spot-check a representative subset of touched files under hipRTC compilation to confirm no remaining hipRTC-blocking content (optional, since the static byte check is a sufficient condition for hipRTC preprocessor acceptance on this dimension). 🤖 Generated with [Claude Code](https://claude.com/claude-code)
225 lines
8.9 KiB
C++
225 lines
8.9 KiB
C++
// Copyright (c) Advanced Micro Devices, Inc., or its affiliates.
|
|
// SPDX-License-Identifier: MIT
|
|
//
|
|
// Schema compatibility tests: frozen baseline configs from example 04.
|
|
//
|
|
// These tests verify that schema changes (new fields, modified defaults,
|
|
// validation rules) do NOT break existing variants. Each test freezes the
|
|
// exact makeSpec() call from a .hip variant file and asserts on the
|
|
// full GemmSpec output.
|
|
//
|
|
// If a test fails after a schema change, the change is NOT backwards-
|
|
// compatible. Fix the schema or update the variant (and document why).
|
|
|
|
#include <rocm_ck/gemm_spec.hpp>
|
|
|
|
#include <gtest/gtest.h>
|
|
|
|
using ::rocm_ck::AddOp;
|
|
using ::rocm_ck::DataType;
|
|
using ::rocm_ck::EpilogueOp;
|
|
using ::rocm_ck::GemmAlgorithm;
|
|
using ::rocm_ck::GemmOp;
|
|
using ::rocm_ck::GemmSpec;
|
|
using ::rocm_ck::Layout;
|
|
using ::rocm_ck::makeSpec;
|
|
using ::rocm_ck::ReluOp;
|
|
using ::rocm_ck::Signature;
|
|
using ::rocm_ck::TargetSet;
|
|
|
|
// Frozen baseline tests: these assert ALL fields of each spec variant.
|
|
// This is intentionally brittle -- adding a new field to GemmSpec will
|
|
// break these tests, forcing explicit review of the change's impact on
|
|
// existing variants. Update the expected values when making intentional
|
|
// schema changes.
|
|
|
|
// ============================================================================
|
|
// gemm_fp32: FP32 plain GEMM, 16x16x16 MFMA tile
|
|
// ============================================================================
|
|
|
|
TEST(SchemaCompat, GemmFP32)
|
|
{
|
|
constexpr auto k = makeSpec(
|
|
Signature{.dtype = DataType::FP32, .ops = {GemmOp{.lhs = "A", .rhs = "B", .out = "C"}}},
|
|
GemmAlgorithm{
|
|
.block_tile = {128, 128, 32}, .block_waves = {2, 2, 1}, .wave_tile = {16, 16, 16}},
|
|
TargetSet::cdna());
|
|
|
|
EXPECT_EQ(k.num_physical_tensors, 3);
|
|
EXPECT_EQ(slot(k, "A"), 0);
|
|
EXPECT_EQ(slot(k, "B"), 1);
|
|
EXPECT_EQ(slot(k, "C"), 2);
|
|
EXPECT_EQ(dtype(k, "A"), DataType::FP32);
|
|
EXPECT_EQ(dtype(k, "B"), DataType::FP32);
|
|
EXPECT_EQ(dtype(k, "C"), DataType::FP32);
|
|
EXPECT_EQ(layout(k, "A"), Layout::Row);
|
|
EXPECT_EQ(layout(k, "B"), Layout::Col);
|
|
EXPECT_EQ(layout(k, "C"), Layout::Row);
|
|
EXPECT_EQ(k.acc_dtype, DataType::FP32);
|
|
EXPECT_EQ(k.num_epilogue_ops, 0);
|
|
EXPECT_EQ(k.workgroup_size, 256);
|
|
EXPECT_EQ(k.wave_tile.m, 16);
|
|
EXPECT_EQ(k.wave_tile.n, 16);
|
|
EXPECT_EQ(k.wave_tile.k, 16);
|
|
}
|
|
|
|
// ============================================================================
|
|
// gemm_fp16: FP16 plain GEMM, 16x16x16 MFMA tile
|
|
// ============================================================================
|
|
|
|
TEST(SchemaCompat, GemmFP16)
|
|
{
|
|
constexpr auto k = makeSpec(
|
|
Signature{.dtype = DataType::FP16, .ops = {GemmOp{.lhs = "A", .rhs = "B", .out = "C"}}},
|
|
GemmAlgorithm{
|
|
.block_tile = {128, 128, 32}, .block_waves = {2, 2, 1}, .wave_tile = {16, 16, 16}},
|
|
TargetSet::cdna());
|
|
|
|
EXPECT_EQ(k.num_physical_tensors, 3);
|
|
EXPECT_EQ(slot(k, "A"), 0);
|
|
EXPECT_EQ(slot(k, "B"), 1);
|
|
EXPECT_EQ(slot(k, "C"), 2);
|
|
EXPECT_EQ(dtype(k, "A"), DataType::FP16);
|
|
EXPECT_EQ(dtype(k, "B"), DataType::FP16);
|
|
EXPECT_EQ(dtype(k, "C"), DataType::FP16);
|
|
EXPECT_EQ(layout(k, "A"), Layout::Row);
|
|
EXPECT_EQ(layout(k, "B"), Layout::Col);
|
|
EXPECT_EQ(layout(k, "C"), Layout::Row);
|
|
EXPECT_EQ(k.acc_dtype, DataType::FP32);
|
|
EXPECT_EQ(k.num_epilogue_ops, 0);
|
|
EXPECT_EQ(k.workgroup_size, 256);
|
|
EXPECT_EQ(k.wave_tile.m, 16);
|
|
EXPECT_EQ(k.wave_tile.n, 16);
|
|
EXPECT_EQ(k.wave_tile.k, 16);
|
|
}
|
|
|
|
// ============================================================================
|
|
// gemm_bf16: BF16 plain GEMM, 16x16x16 MFMA tile
|
|
// ============================================================================
|
|
|
|
TEST(SchemaCompat, GemmBF16)
|
|
{
|
|
constexpr auto k = makeSpec(
|
|
Signature{.dtype = DataType::BF16, .ops = {GemmOp{.lhs = "A", .rhs = "B", .out = "C"}}},
|
|
GemmAlgorithm{
|
|
.block_tile = {128, 128, 32}, .block_waves = {2, 2, 1}, .wave_tile = {16, 16, 16}},
|
|
TargetSet::cdna());
|
|
|
|
EXPECT_EQ(k.num_physical_tensors, 3);
|
|
EXPECT_EQ(slot(k, "A"), 0);
|
|
EXPECT_EQ(slot(k, "B"), 1);
|
|
EXPECT_EQ(slot(k, "C"), 2);
|
|
EXPECT_EQ(dtype(k, "A"), DataType::BF16);
|
|
EXPECT_EQ(dtype(k, "B"), DataType::BF16);
|
|
EXPECT_EQ(dtype(k, "C"), DataType::BF16);
|
|
EXPECT_EQ(layout(k, "A"), Layout::Row);
|
|
EXPECT_EQ(layout(k, "B"), Layout::Col);
|
|
EXPECT_EQ(layout(k, "C"), Layout::Row);
|
|
EXPECT_EQ(k.acc_dtype, DataType::FP32);
|
|
EXPECT_EQ(k.num_epilogue_ops, 0);
|
|
EXPECT_EQ(k.workgroup_size, 256);
|
|
EXPECT_EQ(k.wave_tile.m, 16);
|
|
EXPECT_EQ(k.wave_tile.n, 16);
|
|
EXPECT_EQ(k.wave_tile.k, 16);
|
|
}
|
|
|
|
// ============================================================================
|
|
// gemm_fp16_w32: FP16 plain GEMM, 32x32x16 MFMA tile
|
|
// ============================================================================
|
|
|
|
TEST(SchemaCompat, GemmFP16W32)
|
|
{
|
|
constexpr auto k = makeSpec(
|
|
Signature{.dtype = DataType::FP16, .ops = {GemmOp{.lhs = "A", .rhs = "B", .out = "C"}}},
|
|
GemmAlgorithm{
|
|
.block_tile = {128, 128, 32}, .block_waves = {2, 2, 1}, .wave_tile = {32, 32, 16}},
|
|
TargetSet::cdna());
|
|
|
|
EXPECT_EQ(k.num_physical_tensors, 3);
|
|
EXPECT_EQ(slot(k, "A"), 0);
|
|
EXPECT_EQ(slot(k, "B"), 1);
|
|
EXPECT_EQ(slot(k, "C"), 2);
|
|
EXPECT_EQ(dtype(k, "A"), DataType::FP16);
|
|
EXPECT_EQ(dtype(k, "B"), DataType::FP16);
|
|
EXPECT_EQ(dtype(k, "C"), DataType::FP16);
|
|
EXPECT_EQ(layout(k, "A"), Layout::Row);
|
|
EXPECT_EQ(layout(k, "B"), Layout::Col);
|
|
EXPECT_EQ(layout(k, "C"), Layout::Row);
|
|
EXPECT_EQ(k.acc_dtype, DataType::FP32);
|
|
EXPECT_EQ(k.num_epilogue_ops, 0);
|
|
EXPECT_EQ(k.workgroup_size, 256);
|
|
EXPECT_EQ(k.wave_tile.m, 32);
|
|
EXPECT_EQ(k.wave_tile.n, 32);
|
|
EXPECT_EQ(k.wave_tile.k, 16);
|
|
}
|
|
|
|
// ============================================================================
|
|
// gemm_fp16_add: FP16 GEMM + Add (1 D tensor)
|
|
// ============================================================================
|
|
|
|
TEST(SchemaCompat, GemmFP16Add)
|
|
{
|
|
constexpr auto k = makeSpec(Signature{.dtype = DataType::FP16,
|
|
.ops = {GemmOp{.lhs = "A", .rhs = "B", .out = "C"},
|
|
AddOp{.lhs = "C", .rhs = "bias", .out = "D"}}},
|
|
GemmAlgorithm{.block_tile = {128, 128, 32},
|
|
.block_waves = {2, 2, 1},
|
|
.wave_tile = {16, 16, 16}},
|
|
TargetSet::cdna());
|
|
|
|
EXPECT_EQ(k.num_physical_tensors, 4);
|
|
EXPECT_EQ(slot(k, "A"), 0);
|
|
EXPECT_EQ(slot(k, "B"), 1);
|
|
EXPECT_EQ(slot(k, "D"), 2); // final output
|
|
EXPECT_EQ(slot(k, "bias"), 3); // D0 slot
|
|
EXPECT_EQ(dtype(k, "A"), DataType::FP16);
|
|
EXPECT_EQ(dtype(k, "B"), DataType::FP16);
|
|
EXPECT_EQ(dtype(k, "D"), DataType::FP16);
|
|
EXPECT_EQ(dtype(k, "bias"), DataType::FP16);
|
|
EXPECT_EQ(layout(k, "A"), Layout::Row);
|
|
EXPECT_EQ(layout(k, "B"), Layout::Col);
|
|
EXPECT_EQ(k.acc_dtype, DataType::FP32);
|
|
EXPECT_EQ(k.num_epilogue_ops, 1);
|
|
EXPECT_TRUE(k.hasEpilogueOp(EpilogueOp::Add));
|
|
EXPECT_EQ(k.workgroup_size, 256);
|
|
EXPECT_EQ(k.wave_tile.m, 16);
|
|
EXPECT_EQ(k.wave_tile.n, 16);
|
|
EXPECT_EQ(k.wave_tile.k, 16);
|
|
}
|
|
|
|
// ============================================================================
|
|
// gemm_fp16_add_relu: FP16 GEMM + Add + Relu (1 D tensor)
|
|
// ============================================================================
|
|
|
|
TEST(SchemaCompat, GemmFP16AddRelu)
|
|
{
|
|
constexpr auto k = makeSpec(Signature{.dtype = DataType::FP16,
|
|
.ops = {GemmOp{.lhs = "A", .rhs = "B", .out = "C"},
|
|
AddOp{.lhs = "C", .rhs = "bias", .out = "D"},
|
|
ReluOp{.in = "D", .out = "E"}}},
|
|
GemmAlgorithm{.block_tile = {128, 128, 32},
|
|
.block_waves = {2, 2, 1},
|
|
.wave_tile = {16, 16, 16}},
|
|
TargetSet::cdna());
|
|
|
|
EXPECT_EQ(k.num_physical_tensors, 4);
|
|
EXPECT_EQ(slot(k, "A"), 0);
|
|
EXPECT_EQ(slot(k, "B"), 1);
|
|
EXPECT_EQ(slot(k, "E"), 2); // final output
|
|
EXPECT_EQ(slot(k, "bias"), 3); // D0 slot
|
|
EXPECT_EQ(dtype(k, "A"), DataType::FP16);
|
|
EXPECT_EQ(dtype(k, "B"), DataType::FP16);
|
|
EXPECT_EQ(dtype(k, "E"), DataType::FP16);
|
|
EXPECT_EQ(dtype(k, "bias"), DataType::FP16);
|
|
EXPECT_EQ(layout(k, "A"), Layout::Row);
|
|
EXPECT_EQ(layout(k, "B"), Layout::Col);
|
|
EXPECT_EQ(k.acc_dtype, DataType::FP32);
|
|
EXPECT_EQ(k.num_epilogue_ops, 2);
|
|
EXPECT_TRUE(k.hasEpilogueOp(EpilogueOp::Add));
|
|
EXPECT_TRUE(k.hasEpilogueOp(EpilogueOp::Relu));
|
|
EXPECT_EQ(k.workgroup_size, 256);
|
|
EXPECT_EQ(k.wave_tile.m, 16);
|
|
EXPECT_EQ(k.wave_tile.n, 16);
|
|
EXPECT_EQ(k.wave_tile.k, 16);
|
|
}
|