Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .buildkite/pipeline.yml
Original file line number Diff line number Diff line change
Expand Up @@ -239,7 +239,7 @@ steps:
using CUDA
@assert !CUDA.functional()
@assert !isdefined(CUDACore, :libcudart)
CUDA.set_runtime_version!(v"11.6")'
CUDA.set_runtime_version!(v"12.0")'
julia --project -e '
using CUDA
@assert !CUDA.functional()
Expand Down
2 changes: 1 addition & 1 deletion CUDACore/Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ ChainRulesCore = "1"
EnzymeCore = "0.8.2"
ExprTools = "0.1"
GPUArrays = "11.5.4"
GPUCompiler = "2"
GPUCompiler = "2.1"
GPUToolbox = "3"
KernelAbstractions = "0.9.38"
LLVM = "9.6"
Expand Down
35 changes: 31 additions & 4 deletions CUDACore/src/compiler/compilation.jl
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ end
const CUDACompilerConfig = CompilerConfig{PTXCompilerTarget, CUDACompilerParams}
const AnyCUDAJob = CompilerJob{PTXCompilerTarget, <:AbstractCUDACompilerParams}

# CUDA 12.0 is the oldest supported toolkit, and Maxwell is the oldest supported GPU.
const minreq = (; ptx=v"8.0", sm=sm"50")

GPUCompiler.runtime_module(@nospecialize(job::AnyCUDAJob)) = CUDACore

# filter out functions from libdevice and cudadevrt
Expand Down Expand Up @@ -56,6 +59,23 @@ function GPUCompiler.finish_module!(@nospecialize(job::AnyCUDAJob),
Tuple{CompilerJob{PTXCompilerTarget}, LLVM.Module, LLVM.Function},
job, mod, entry)

# Make the compilation target available to device code. GPUCompiler used to provide
# these globals, but target-specific properties are owned by the back-end now.
feature_set = job.config.target.feature_set === :arch ? ArchFeatures :
job.config.target.feature_set === :family ? FamilyFeatures :
BaselineFeatures
for (name, value) in ["sm_major" => job.config.target.cap.major,
"sm_minor" => job.config.target.cap.minor,
"sm_features" => UInt32(feature_set),
"ptx_major" => job.config.target.ptx.major,
"ptx_minor" => job.config.target.ptx.minor]
if haskey(globals(mod), name)
gv = globals(mod)[name]
initializer!(gv, ConstantInt(LLVM.Int32Type(), value))
linkage!(gv, LLVM.API.LLVMPrivateLinkage)
end
end

# if this kernel uses our RNG, we should prime the shared state.
# XXX: these transformations should really happen at the Julia IR level...
if haskey(globals(mod), "global_random_keys")
Expand Down Expand Up @@ -196,7 +216,7 @@ function compiler_config(dev; kwargs...)
end

function default_ptx_versions(llvm_support, ptxas_support)
requested_ptx = v"6.2"
requested_ptx = minreq.ptx
llvm_ptxs = filter(>=(requested_ptx), llvm_support.ptx)
ptxas_ptxs = filter(>=(requested_ptx), ptxas_support.ptx)
isempty(llvm_ptxs) &&
Expand Down Expand Up @@ -235,13 +255,15 @@ end
# determine the PTX ISA to use.
if ptx !== nothing
# explicit request: take it exactly, validating against the toolchain
ptx >= minreq.ptx ||
error("CUDA.jl requires PTX ISA $(minreq.ptx) or higher")
ptx in llvm_support.ptx ||
error("Requested PTX ISA $ptx is not supported by LLVM $(nvptx_llvm_version)")
ptx in ptxas_support.ptx ||
error("Requested PTX ISA $ptx is not supported by ptxas $(compiler_version())")
llvm_ptx = ptxas_ptx = ptx
else
# default: pick the newest PTX ISA supported by the toolchain (>=v6.2)
# default: pick the newest supported PTX ISA at or above our minimum
llvm_ptx, ptxas_ptx = default_ptx_versions(llvm_support, ptxas_support)
end

Expand All @@ -255,14 +277,18 @@ end
ptx_sms = ptx_sm_support(ptxas_ptx)
if arch !== nothing
# explicit request: take it as-is, validating against the PTX ISA
base_version(arch) >= base_version(minreq.sm) ||
error("CUDA.jl requires compute capability $(cpu_name(minreq.sm)) or higher")
arch in ptx_sms ||
error("$(cpu_name(arch)) is not supported by PTX ISA $(ptxas_ptx)")
ptxas_sm = arch
else
# pick the most specific capability the selected PTX ISA supports whose cubin
# would actually load on the current device. For baseline that's the onion model;
# `:arch` requires an exact CC match, `:family` a same-family match.
ptxas_candidates = filter(sm -> runs_on(sm, capability(dev)), ptx_sms)
ptxas_candidates = filter(ptx_sms) do sm
base_version(sm) >= base_version(minreq.sm) && runs_on(sm, capability(dev))
end
isempty(ptxas_candidates) &&
error("Compute capability $(capability(dev)) is not supported by ptxas " *
"$(compiler_version()) at PTX ISA $(ptxas_ptx)")
Expand All @@ -275,7 +301,8 @@ end
# Exact `ptxas_sm` unavailable in LLVM. Fall back to baseline LLVM at a
# lower base, since arch/family features don't carry across versions.
baseline_candidates = filter(llvm_support.sm) do sm
sm.feature_set === :baseline && base_version(sm) <= base_version(ptxas_sm)
sm.feature_set === :baseline &&
base_version(minreq.sm) <= base_version(sm) <= base_version(ptxas_sm)
end
isempty(baseline_candidates) &&
error("Compute capability $(cpu_name(ptxas_sm)) is not supported by LLVM $(nvptx_llvm_version)")
Expand Down
41 changes: 10 additions & 31 deletions CUDACore/src/device/intrinsics/atomics.jl
Original file line number Diff line number Diff line change
Expand Up @@ -164,8 +164,11 @@ for A in (AS.Generic, AS.Global, AS.Shared), T in (:Int16, :UInt16)
end

intr = "atom$scope.cas.b16 \$0, [\$1], \$2, \$3;"
@eval @device_function @inline atomic_cas!(ptr::LLVMPtr{$T,$A}, cmp::$T, val::$T) =
@asmcall($intr, "=h,l,h,h", true, $T, Tuple{Core.LLVMPtr{$T,$A},$T,$T}, ptr, cmp, val)
@eval @device_function @inline function atomic_cas!(ptr::LLVMPtr{$T,$A}, cmp::$T, val::$T)
require_sm_70()
@asmcall($intr, "=h,l,h,h", true, $T,
Tuple{Core.LLVMPtr{$T,$A},$T,$T}, ptr, cmp, val)
end
end


Expand Down Expand Up @@ -448,36 +451,12 @@ for (op,impl,typ) in [(:(+), :(atomic_add!), [:UInt32,:Int32,:UInt64,:Int64,:Flo
$impl(pointer(A, I), val)
end

# native atomics that are not supported on all devices
@inline function atomic_arrayset(A::AbstractArray{T}, I::Integer, op::typeof(+),
val::T) where {T <: Union{Float64}}
ptr = pointer(A, I)
if compute_capability() >= sv"6.0"
atomic_add!(ptr, val)
else
atomic_op!(ptr, op, val)
end
end

@inline function atomic_arrayset(A::AbstractArray{Float16}, I::Integer, op::typeof(+),
val::Float16)
ptr = pointer(A, I)
if compute_capability() >= sv"7.0"
atomic_add!(ptr, val)
else
atomic_op!(ptr, op, val)
end
end
# native atomics that the back-end expands on older devices
@inline atomic_arrayset(A::AbstractArray{T}, I::Integer, ::typeof(+), val::T) where
{T <: Union{Float16,Float64}} = atomic_add!(pointer(A, I), val)
@static if VERSION >= v"1.11"
@inline function atomic_arrayset(A::AbstractArray{BFloat16}, I::Integer, op::typeof(+),
val::BFloat16)
ptr = pointer(A, I)
if compute_capability() >= sv"9.0"
atomic_add!(ptr, val)
else
atomic_op!(ptr, op, val)
end
end
@inline atomic_arrayset(A::AbstractArray{BFloat16}, I::Integer, ::typeof(+),
val::BFloat16) = atomic_add!(pointer(A, I), val)
end

# fallback using compare-and-swap
Expand Down
32 changes: 24 additions & 8 deletions CUDACore/src/device/intrinsics/math.jl
Original file line number Diff line number Diff line change
Expand Up @@ -311,33 +311,49 @@ function dp4a end
@static if LLVM.version() >= v"21"
# LLVM 21 added @llvm.nvvm.idp4a.[us].[us]; prefer the intrinsic over inline PTX so
# the instruction participates in optimization and instruction selection.
@device_function dp4a(a::Int32, b::Int32, c::Int32) =
@device_function function dp4a(a::Int32, b::Int32, c::Int32)
require_sm_61()
ccall("llvm.nvvm.idp4a.s.s", llvmcall, Int32, (Int32, Int32, Int32), a, b, c)
end

@device_function dp4a(a::Int32, b::UInt32, c::Int32) =
@device_function function dp4a(a::Int32, b::UInt32, c::Int32)
require_sm_61()
ccall("llvm.nvvm.idp4a.s.u", llvmcall, Int32, (Int32, UInt32, Int32), a, b, c)
end

@device_function dp4a(a::UInt32, b::Int32, c::Int32) =
@device_function function dp4a(a::UInt32, b::Int32, c::Int32)
require_sm_61()
ccall("llvm.nvvm.idp4a.u.s", llvmcall, Int32, (UInt32, Int32, Int32), a, b, c)
end

@device_function dp4a(a::UInt32, b::UInt32, c::UInt32) =
@device_function function dp4a(a::UInt32, b::UInt32, c::UInt32)
require_sm_61()
ccall("llvm.nvvm.idp4a.u.u", llvmcall, UInt32, (UInt32, UInt32, UInt32), a, b, c)
end
else
@device_function dp4a(a::Int32, b::Int32, c::Int32) =
@device_function function dp4a(a::Int32, b::Int32, c::Int32)
require_sm_61()
@asmcall("dp4a.s32.s32 \$0, \$1, \$2, \$3;", "=r,r,r,r", false,
Int32, Tuple{Int32, Int32, Int32}, a, b, c)
end

@device_function dp4a(a::Int32, b::UInt32, c::Int32) =
@device_function function dp4a(a::Int32, b::UInt32, c::Int32)
require_sm_61()
@asmcall("dp4a.s32.u32 \$0, \$1, \$2, \$3;", "=r,r,r,r", false,
Int32, Tuple{Int32, UInt32, Int32}, a, b, c)
end

@device_function dp4a(a::UInt32, b::Int32, c::Int32) =
@device_function function dp4a(a::UInt32, b::Int32, c::Int32)
require_sm_61()
@asmcall("dp4a.u32.s32 \$0, \$1, \$2, \$3;", "=r,r,r,r", false,
Int32, Tuple{UInt32, Int32, Int32}, a, b, c)
end

@device_function dp4a(a::UInt32, b::UInt32, c::UInt32) =
@device_function function dp4a(a::UInt32, b::UInt32, c::UInt32)
require_sm_61()
@asmcall("dp4a.u32.u32 \$0, \$1, \$2, \$3;", "=r,r,r,r", false,
UInt32, Tuple{UInt32, UInt32, UInt32}, a, b, c)
end
end


Expand Down
3 changes: 2 additions & 1 deletion CUDACore/src/device/intrinsics/misc.jl
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,10 @@ end
Puts a thread for a given amount `t`(in nanoseconds).

!!! note
Requires CUDA >= 10.0 and sm_6.2
Requires compute capability 7.0.
"""
@inline function nanosleep(t::Unsigned)
require_sm_70()
@asmcall("nanosleep.u32 \$0;", "r", true,
Cvoid, Tuple{UInt32}, convert(UInt32, t))
end
1 change: 1 addition & 0 deletions CUDACore/src/device/intrinsics/shared_memory.jl
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ end
end

@inline function map_shared_rank(ptr_shared::LLVMPtr{T,AS.Shared}, rank::Integer) where {T}
require_sm_90()
# This requires LLVM >=20 (i.e. Julia >= 1.13)
ptr7 = @asmcall(
"mapa.shared::cluster.u64 \$0, \$1, \$2;",
Expand Down
6 changes: 4 additions & 2 deletions CUDACore/src/device/intrinsics/synchronization.jl
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ visible to those threads in the warp. The default value for `mask` selects all t
the warp.

!!! note
Requires CUDA >= 9.0 and sm_6.2
Requires PTX ISA 6.0 and sm_30.
"""
@inline sync_warp(mask=FULL_MASK) =
ccall("llvm.nvvm.bar.warp.sync", llvmcall, Cvoid, (UInt32,), mask)
Expand Down Expand Up @@ -89,8 +89,10 @@ for (fn, barrier) in ["cluster_arrive" => "arrive",
}
attributes #0 = { convergent nomerge nounwind }
attributes #1 = { alwaysinline }"""
@eval @device_function @inline $(Symbol(fn))() =
@eval @device_function @inline function $(Symbol(fn))()
require_sm_90()
Base.llvmcall(($mod, "entry"), Cvoid, Tuple{})
end
end

## memory barriers (membar)
Expand Down
23 changes: 18 additions & 5 deletions CUDACore/src/device/intrinsics/version.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,13 @@

export compute_capability, ptx_isa_version, target_feature_set

# Wire-format encoding of the feature set, stamped into the `sm_features` LLVM global.
@enum TargetFeatureSet::UInt32 begin
BaselineFeatures = 0
FamilyFeatures = 1
ArchFeatures = 2
end

for var in ["sm_major", "sm_minor", "sm_features", "ptx_major", "ptx_minor"]
@eval @device_function @inline $(Symbol(var))() =
Base.llvmcall(
Expand All @@ -17,16 +24,22 @@ end
@device_function @inline compute_capability() = SimpleVersion(sm_major(), sm_minor())
@device_function @inline ptx_isa_version() = SimpleVersion(ptx_major(), ptx_minor())

for cap in (sv"6.1", sv"7.0", sv"7.2", sv"8.0", sv"9.0")
local requirement = Symbol("require_sm_", cap.major, cap.minor)
local message = "requires compute capability $(cap.major).$(cap.minor) or higher"
@eval @device_function @inline $requirement() =
GPUCompiler.@static_assert compute_capability() >= $cap $message
end

# Feature set encoded in the `.target` directive: one of `:baseline`, `:family`, `:arch`.
# (NVIDIA's PTX ISA reference: ".target specifies the set of features in the target
# architecture for which the current PTX code was generated.") GPUCompiler stamps the
# encoding in via the `sm_features` LLVM global, using `GPUCompiler.TargetFeatureSet`;
# architecture for which the current PTX code was generated.") CUDA's compiler stamps the
# encoding in via the `sm_features` LLVM global, using `TargetFeatureSet`;
# the integer load + chained compare folds away after LLVM inlines the constant, so
# user code like `if target_feature_set() === :arch ... end` resolves to a single
# branch in the PTX output.
@device_function @inline function target_feature_set()
f = sm_features()
return f == UInt32(GPUCompiler.ArchFeatures) ? :arch :
f == UInt32(GPUCompiler.FamilyFeatures) ? :family : :baseline
return f == UInt32(ArchFeatures) ? :arch :
f == UInt32(FamilyFeatures) ? :family : :baseline
end

41 changes: 35 additions & 6 deletions CUDACore/src/device/intrinsics/wmma.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ export WMMA
module WMMA

import ..LLVM
using ..CUDACore: AS, @device_function
using ..CUDACore: AS, @device_function, require_sm_70, require_sm_72, require_sm_80
using Core: LLVMPtr
using BFloat16s: BFloat16

Expand Down Expand Up @@ -212,12 +212,23 @@ for ops in all_ldst_ops,
ccall_name = "$llvm_intr"

ptr_ty = :(LLVMPtr{$arr_ty, $addr_space_int})
req = elem_type == "bf16" ? :(require_sm_80()) :
elem_type in ("u8", "s8", "s32") ? :(require_sm_72()) : :(require_sm_70())

if sz == 1
@eval @device_function $func_name(src_addr, stride) = tuple(ccall($ccall_name, llvmcall, $frag_ty, ($ptr_ty, Int32), src_addr, stride))
@eval @device_function function $func_name(src_addr, stride)
$req
tuple(ccall($ccall_name, llvmcall, $frag_ty,
($ptr_ty, Int32), src_addr, stride))
end
else
struct_ty = Symbol("LLVMStruct$sz")
@eval @device_function $func_name(src_addr, stride) = convert(NTuple{$sz, $frag_ty}, ccall($ccall_name, llvmcall, $struct_ty{$frag_ty}, ($ptr_ty, Int32), src_addr, stride))
@eval @device_function function $func_name(src_addr, stride)
$req
convert(NTuple{$sz, $frag_ty},
ccall($ccall_name, llvmcall, $struct_ty{$frag_ty},
($ptr_ty, Int32), src_addr, stride))
end
end
@eval export $func_name
@eval @doc (@doc llvm_wmma_load) $func_name
Expand Down Expand Up @@ -283,8 +294,13 @@ export llvm_wmma_store
frag_vars = ntuple(i -> :(data[$i]), sz)

ptr_ty = :(LLVMPtr{$arr_ty, $addr_space_int})
req = elem_type == "s32" ? :(require_sm_72()) : :(require_sm_70())

@eval @device_function $func_name(dst_addr, data, stride) = ccall($ccall_name, llvmcall, Nothing, ($ptr_ty, $(frag_types...), Int32), dst_addr, $(frag_vars...), stride)
@eval @device_function function $func_name(dst_addr, data, stride)
$req
ccall($ccall_name, llvmcall, Nothing,
($ptr_ty, $(frag_types...), Int32), dst_addr, $(frag_vars...), stride)
end
@eval export $func_name
@eval @doc (@doc llvm_wmma_store) $func_name
end
Expand Down Expand Up @@ -359,12 +375,25 @@ for ops in all_wmma_ops,
a_vars = ntuple(i -> :(a[$i]), a_sz)
b_vars = ntuple(i -> :(b[$i]), b_sz)
c_vars = ntuple(i -> :(c[$i]), c_sz)
req = a_elem_type == "bf16" ? :(require_sm_80()) :
a_elem_type in ("u8", "s8") ? :(require_sm_72()) : :(require_sm_70())

if d_sz == 1
@eval @device_function $func_name(a, b, c) = tuple(ccall($ccall_name, llvmcall, $d_frag_ty, ($(a_types...), $(b_types...), $(c_types...)), $(a_vars...), $(b_vars...), $(c_vars...)))
@eval @device_function function $func_name(a, b, c)
$req
tuple(ccall($ccall_name, llvmcall, $d_frag_ty,
($(a_types...), $(b_types...), $(c_types...)),
$(a_vars...), $(b_vars...), $(c_vars...)))
end
else
struct_ty = Symbol("LLVMStruct$d_sz")
@eval @device_function $func_name(a, b, c) = convert(NTuple{$d_sz, $d_frag_ty}, ccall($ccall_name, llvmcall, $struct_ty{$d_frag_ty}, ($(a_types...), $(b_types...), $(c_types...)), $(a_vars...), $(b_vars...), $(c_vars...)))
@eval @device_function function $func_name(a, b, c)
$req
convert(NTuple{$d_sz, $d_frag_ty},
ccall($ccall_name, llvmcall, $struct_ty{$d_frag_ty},
($(a_types...), $(b_types...), $(c_types...)),
$(a_vars...), $(b_vars...), $(c_vars...)))
end
end
@eval export $func_name
@eval @doc (@doc llvm_wmma_mma) $func_name
Expand Down
Loading