This is an automated email from the ASF dual-hosted git repository.
tqchen pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/tvm-tirx.git
The following commit(s) were added to refs/heads/main by this push:
new 642ff3fc7 [FIX][CUDA] Correct vector storage, alignment, and FP8 casts
(#68)
642ff3fc7 is described below
commit 642ff3fc7d8aa4ad4bd66ddc28b2d1e886f47b5d
Author: Tianqi Chen <[email protected]>
AuthorDate: Sun Oct 25 14:30:57 2026 -0400
[FIX][CUDA] Correct vector storage, alignment, and FP8 casts (#68)
---
src/backend/cuda/codegen/codegen_cuda.cc | 71 ++++++++++++++++++++--
src/backend/cuda/codegen/codegen_cuda.h | 3 +
tests/python/backend/cuda/codegen/test_fp8.py | 20 ++++++
.../backend/cuda/codegen/test_vector_storage.py | 62 +++++++++++++++++++
tests/python/codegen/gpu_common/test_intrinsics.py | 5 +-
5 files changed, 155 insertions(+), 6 deletions(-)
diff --git a/src/backend/cuda/codegen/codegen_cuda.cc
b/src/backend/cuda/codegen/codegen_cuda.cc
index bed6b7d66..f0227c604 100644
--- a/src/backend/cuda/codegen/codegen_cuda.cc
+++ b/src/backend/cuda/codegen/codegen_cuda.cc
@@ -589,10 +589,10 @@ void CodeGenCUDA::PrintType(const PrimType& t,
std::ostream& os) { // NOLINT(*)
return;
} else if (t.MatchesCode(DLDataTypeCode::kDLBool) && lanes > 1) {
// CUDA does not support bool vectors.
- // Use ushort vectors to represent instead.
+ // Use one byte per element, matching scalar bool tensor storage.
int n = lanes;
if (n <= 4) {
- os << "ushort" << n;
+ os << "uchar" << n;
return;
}
} else if (t.MatchesCode(DLDataTypeCode::kDLUInt, DLDataTypeCode::kDLInt)) {
@@ -878,6 +878,36 @@ void CodeGenCUDA::PrintVecBinaryOp(const std::string& op,
const PrimType& t, Pri
os << sret;
}
+std::string CodeGenCUDA::GetBufferRef(const PrimType& t, const VarObj* buffer,
PrimExpr index) {
+ if (!t.IsScalar()) {
+ // CUDA vector representations align to their power-of-two storage
component,
+ // capped at 16 bytes. Bool and FP6 wrappers use one byte per scalar
element.
+ int bits = t.MatchesCode(DLDataTypeCode::kDLBool) || IsFloat6(t) ? 8 :
t.bits();
+ int bytes = (bits * t.lanes() + 7) / 8;
+ int required_alignment = std::min(16, bytes & -bytes);
+ int buffer_alignment =
ffi::GetRef<Var>(buffer).as_or_throw<TensorVar>().ty()->data_alignment;
+ if (buffer_alignment % required_alignment != 0) {
+ TVM_FFI_THROW(ValueError) << "Cannot prove alignment for CUDA vector
access of " << t
+ << ": buffer alignment " << buffer_alignment
+ << " is not a multiple of " <<
required_alignment << " bytes";
+ }
+ }
+ return CodeGenC::GetBufferRef(t, buffer, index);
+}
+
+void CodeGenCUDA::PrintVecStore(const VarObj* buffer, const PrimType& t,
PrimExpr base,
+ const std::string& value) {
+ // Match the conservative offset-alignment proof used for vector loads.
+ // A typed CUDA vector dereference must not reach an unaligned address.
+ sym::ModularSet alignment = sym::Analyzer()->modular_set(base);
+ if (alignment->coeff % t.lanes() != 0 || alignment->base % t.lanes() != 0) {
+ TVM_FFI_THROW(ValueError) << "Cannot prove alignment for CUDA vector store
of " << t
+ << ": base index must be a multiple of " <<
t.lanes() << ", but got "
+ << base;
+ }
+ CodeGenC::PrintVecStore(buffer, t, base, value);
+}
+
void CodeGenCUDA::PrintVecElemLoad(const std::string& vec, const PrimType& t,
int i,
std::ostream& os) { // NOLINT(*)
int lanes = t.lanes();
@@ -1054,6 +1084,39 @@ void CodeGenCUDA::Dispatch_(const prim::CastObj* op,
std::ostream& os) {
PrimType target_ty = op->ty.as_or_throw<PrimType>();
TVM_FFI_ICHECK_EQ(target_ty.lanes(), from_ty.lanes());
+ // Distinct FP8 formats require a numeric conversion even when their CUDA
+ // vector storage types coincide. CUDA's FP8 wrappers do not provide direct
+ // cross-format constructors; float is an exact intermediate for FP8 values.
+ if (IsFloat8(from_ty) && IsFloat8(target_ty) && from_ty.code() !=
target_ty.code()) {
+ std::string source = PrintExprToSSA(op->value);
+ PrimType from_scalar = from_ty.WithLanes(1);
+ PrimType target_scalar = target_ty.WithLanes(1);
+ if (from_ty.IsScalar()) {
+ PrintType(target_scalar, os);
+ os << "(float(" << source << "))";
+ return;
+ }
+ std::string result = name_supply_->FreshName("_");
+ PrintIndent();
+ PrintType(target_ty, stream);
+ stream << " " << result << ";\n";
+ for (int i = 0; i < from_ty.lanes(); ++i) {
+ std::string lane = name_supply_->FreshName("_");
+ PrintIndent();
+ PrintType(from_scalar, stream);
+ stream << " " << lane << ";\n";
+ PrintIndent();
+ stream << lane << ".__x = reinterpret_cast<const unsigned char*>(&" <<
source << ")[" << i
+ << "];\n";
+ PrintIndent();
+ stream << "reinterpret_cast<unsigned char*>(&" << result << ")[" << i <<
"] = ";
+ PrintType(target_scalar, stream);
+ stream << "(float(" << lane << ")).__x;\n";
+ }
+ os << result;
+ return;
+ }
+
// Emit simple C-style type conversion.
if (from_ty.IsScalar()) return CodeGenC::Dispatch_(op, os);
@@ -1599,9 +1662,9 @@ void CodeGenCUDA::Dispatch_(const prim::SelectObj* op,
std::ostream& os) {
std::string t_var = PrintExprToSSA(op->true_value);
std::string f_var = PrintExprToSSA(op->false_value);
- // The condition is stored as an ushort vector.
+ // Use the condition's bool-vector representation.
int lanes = op_ty.lanes();
- PrimType memory_ty = PrimType::UInt(16, lanes);
+ PrimType memory_ty = op->condition.ty();
for (int i = 0; i < lanes; ++i) {
std::ostringstream item;
diff --git a/src/backend/cuda/codegen/codegen_cuda.h
b/src/backend/cuda/codegen/codegen_cuda.h
index f8292bab0..e502d3c0f 100644
--- a/src/backend/cuda/codegen/codegen_cuda.h
+++ b/src/backend/cuda/codegen/codegen_cuda.h
@@ -71,6 +71,9 @@ class CodeGenCUDA final : public CodeGenC {
std::ostream& os) final; // NOLINT(*)
void PrintVecElemStore(const std::string& vec, const PrimType& t, int i,
const std::string& value) final;
+ void PrintVecStore(const VarObj* buffer, const PrimType& t, PrimExpr base,
+ const std::string& value) final;
+ std::string GetBufferRef(const PrimType& t, const VarObj* buffer, PrimExpr
index) final;
void BindThreadIndex(const PrimVar& var, const ffi::String& thread_tag)
final; // NOLINT(*)
void PrintVecElemLoadExpr(const PrimType& t, int i, const std::string& value,
std::ostream& os) final;
diff --git a/tests/python/backend/cuda/codegen/test_fp8.py
b/tests/python/backend/cuda/codegen/test_fp8.py
index 59b52b60c..473e75e2f 100644
--- a/tests/python/backend/cuda/codegen/test_fp8.py
+++ b/tests/python/backend/cuda/codegen/test_fp8.py
@@ -609,5 +609,25 @@ def test_fp8_fp16_bf16_vectorize_arith(vec_length, dtype):
tvm_tirx.testing.run_with_gpu_lock(run_and_check)
[email protected]
[email protected](not env.has_cuda_compute(9), reason="need CUDA compute >=
9.0")
+def test_cross_format_fp8_conversion():
+ @T.function
+ def main(A: T.Tensor((8,), "float8_e4m3fn"), B: T.Tensor((8,),
"float8_e5m2")):
+ T.func_attr({"tirx.noalias": True})
+ with T.launch_thread("blockIdx.x", 1):
+ with T.launch_thread("threadIdx.x", 1):
+ B[T.Ramp(0, 1, 8)] = T.Cast("float8_e5m2x8", A[T.Ramp(0, 1,
8)])
+
+ compiled = tvm_tirx.tirx.compile(main, target="cuda")
+ values = np.array([0.0, 1.0, -1.0, 2.5, -2.5, 0.125, -0.125, 240.0],
dtype="float8_e4m3fn")
+ device = tvm_tirx.cuda()
+ output = tvm_tirx.runtime.empty((8,), "float8_e5m2", device)
+ compiled(tvm_tirx.runtime.tensor(values, device), output)
+ np.testing.assert_array_equal(
+ output.numpy().astype("float32"),
values.astype("float8_e5m2").astype("float32")
+ )
+
+
if __name__ == "__main__":
tvm_tirx.testing.main()
diff --git a/tests/python/backend/cuda/codegen/test_vector_storage.py
b/tests/python/backend/cuda/codegen/test_vector_storage.py
new file mode 100644
index 000000000..fad03231f
--- /dev/null
+++ b/tests/python/backend/cuda/codegen/test_vector_storage.py
@@ -0,0 +1,62 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+import numpy as np
+import pytest
+
+import tvm_tirx as tx
+from tvm_tirx.script import tirx as T
+from tvm_tirx.testing import env
+
+
[email protected]
[email protected](not env.has_cuda(), reason="need CUDA")
+def test_bool_storage_uses_one_byte_per_lane():
+ @T.function
+ def compare(
+ A: T.Tensor((32,), "int32"),
+ B: T.Tensor((32,), "int32"),
+ C: T.Tensor((40,), "bool"),
+ ):
+ T.func_attr({"tirx.noalias": True})
+ with T.launch_thread("blockIdx.x", 1):
+ with T.launch_thread("threadIdx.x", 8) as tx:
+ C[T.Ramp(4 + tx * 4, 1, 4)] = A[T.Ramp(tx * 4, 1, 4)] <
B[T.Ramp(tx * 4, 1, 4)]
+
+ device = tx.cuda()
+ a = np.arange(32, dtype="int32") % 3
+ b = np.ones(32, dtype="int32")
+ expected = np.ones(40, dtype="bool")
+ expected[4:-4] = a < b
+ result = tx.runtime.tensor(np.ones_like(expected), device)
+ compiled = tx.tirx.compile(compare, target="cuda")
+ compiled(tx.runtime.tensor(a, device), tx.runtime.tensor(b, device),
result)
+ np.testing.assert_array_equal(result.numpy(), expected)
+
+
[email protected]
[email protected](not env.has_cuda(), reason="need CUDA")
+def test_unaligned_vector_store_is_rejected():
+ @T.function
+ def copy(A: T.Tensor((32,), "int32"), B: T.Tensor((33,), "int32")):
+ T.func_attr({"tirx.noalias": True})
+ with T.launch_thread("blockIdx.x", 1):
+ with T.launch_thread("threadIdx.x", 8) as tx:
+ B[T.Ramp(tx * 4 + 1, 1, 4)] = A[T.Ramp(tx * 4, 1, 4)]
+
+ with pytest.raises(ValueError, match="Cannot prove alignment for CUDA
vector store"):
+ tx.tirx.compile(copy, target="cuda")
diff --git a/tests/python/codegen/gpu_common/test_intrinsics.py
b/tests/python/codegen/gpu_common/test_intrinsics.py
index 1726c5380..7802ba1e4 100644
--- a/tests/python/codegen/gpu_common/test_intrinsics.py
+++ b/tests/python/codegen/gpu_common/test_intrinsics.py
@@ -44,7 +44,7 @@ def test_int_intrin(target, dtype):
if not tvm_tirx.testing.device_enabled(target):
pytest.skip(f"{target} not enabled")
test_funcs = [
- (T.clz, lambda x, dtype: int(dtype[-2:]) - (len(bin(x)) - 2)),
+ (T.clz, lambda x, dtype: int(dtype[-2:]) - int(x).bit_length()),
]
for tvm_intrin, np_func in test_funcs:
@@ -65,7 +65,8 @@ def test_int_intrin(target, dtype):
def run_and_check():
dev = tvm_tirx.device_from_target(target)
- a = tvm_tirx.runtime.tensor(np.random.randint(0, 100000,
size=n).astype(dtype), dev)
+ # clz is undefined for zero.
+ a = tvm_tirx.runtime.tensor(np.random.randint(1, 100000,
size=n).astype(dtype), dev)
b = tvm_tirx.runtime.tensor(np.zeros(shape=(n,)).astype(dtype),
dev)
f(a, b)
ref = np.vectorize(partial(np_func, dtype=dtype))(a.numpy())