Skip to content

Commit 9115a6a

Browse files
committed
expand rand/randn convenience APIs and align docs/tests. Move randn open-interval helpers into utilities.jl and switch naming of internals. Simplify rand!/randn! wrappers and add convenience constructors for omitted rng, backend, or type, with backend-dependent defaults (Float64 on CPU, Float32 otherwise). Add explicit zero-arg guards so rand()/randn() require at least one dimension. Update rand.md with concise convenience semantics and examples, including type-only no-rng calls, plus doc entries for rand/randn. Expand tests to cover default-type dispatch, CPU fallback routes, typed no-rng overloads, and invalid-signature/kwarg throw behavior while preserving deterministic CounterRNG offset progression.
1 parent 77625cb commit 9115a6a

10 files changed

Lines changed: 324 additions & 153 deletions

File tree

docs/src/api/rand.md

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@
33
Counter-based random generation for CPU and GPU backends with deterministic stream behavior for
44
fixed `seed`, algorithm, and call sequence.
55

6+
Both in-place and allocation forms are supported:
7+
- Uniform: `AK.rand!`, `AK.rand`
8+
- Standard normal: `AK.randn!`, `AK.randn`
9+
610
`CounterRNG` carries an internal `offset` (starting at `0`) that advances by `length(v)` on each
711
`AK.rand!(rng, v)` call. This means chunked fills are stream-consistent:
812
- filling `100` then `100` elements yields the same `200` values as one `200`-element fill.
@@ -16,9 +20,16 @@ Use an explicit `CounterRNG` when reproducibility is required. For
1620
convenience,
1721
`AK.rand!(v)` creates a fresh `CounterRNG()` on each call using one auto-seeded
1822
`Base.rand(UInt64)` draw, so repeated calls produce different outputs unless Random.seed!() is used.
23+
Likewise, `AK.rand(backend, args...)` creates a fresh auto-seeded `CounterRNG()` on each call.
1924

2025
`AK.reset!(rng::AK.CounterRNG)` rewinds `rng.offset` to `0x0`.
2126

27+
Allocation convenience:
28+
- Canonical forms are `AK.rand(rng, backend, T, dims...)` and `AK.randn(rng, backend, T, dims...)`.
29+
- Defaults are shared: omit `rng` -> fresh `CounterRNG()`; omit `backend` -> CPU backend; omit `T` -> `Float64` on CPU backend and `Float32` otherwise.
30+
- Common shorthands include `AK.rand(dims...)`, `AK.rand(T, dims...)`, `AK.rand(backend, dims...)`, and the corresponding `AK.randn(...)` variants.
31+
- For explicit `rng`, both `AK.rand` and `AK.randn` advance `rng.offset` by `prod(dims)`.
32+
2233
Custom algorithms:
2334
- Define an algorithm type `MyAlg <: AK.CounterRNGAlgorithm`.
2435
- Implement typed `rand_uint` methods:
@@ -39,6 +50,9 @@ Supported element types:
3950

4051
`AK.randn!` uses Box-Muller with open-interval uniforms in `(0, 1)` from a branch-free midpoint mapping.
4152

53+
`AK.randn!(v)` and `AK.randn(backend, args...)` create a fresh auto-seeded `CounterRNG()` on each
54+
call, so repeated calls produce different outputs unless `Random.seed!()` is used.
55+
4256
The core of the random number generation produces either a `UInt32` or `UInt64` depending on the width of the requested element type.
4357
That `UInt` is then either:
4458
- Unsigned integers: returned as-is or truncated if necessary.
@@ -72,6 +86,7 @@ Examples:
7286
```julia
7387
import AcceleratedKernels as AK
7488
using oneAPI
89+
using ROCArray
7590

7691
# Reproducible
7792
rng = AK.CounterRNG(0x12345678; alg=AK.Philox())
@@ -88,15 +103,27 @@ AK.rand!(rng, v2)
88103
y = oneArray{Float32}(undef, 1024)
89104
AK.rand!(y)
90105

91-
# Standard normal samples
92-
z = oneArray{Float32}(undef, 1024)
106+
# Allocation form
107+
y_cpu_auto = AK.rand(1024) # defaults to CPU, Vector{Float64}
108+
y_oneArray = AK.rand(oneAPIBackend(), Float32, 1024) # fresh RNG, allocate and fill oneArray
109+
y_cpu_typed = AK.rand(rng, Float16, 1024) # CPU backend, explicit type, explicit RNG
110+
111+
# Standard normal filling
112+
z = ROCArray{Float32}(undef, 1024)
93113
AK.randn!(rng, z)
114+
115+
# Standard normal allocation form
116+
z_cpu_auto = AK.randn(1024) # defaults to CPU, Vector{Float64}
117+
z_ROCArray = AK.randn(oneAPIBackend(), 1024) # allocate and fill ROCArray{Float32}
118+
z_cpu_typed = AK.randn(rng, Float16, 1024) # CPU backend, explicit type, explicit RNG
94119
```
95120

96121
```@docs
97122
AcceleratedKernels.CounterRNG
98123
AcceleratedKernels.CounterRNGAlgorithm
99124
AcceleratedKernels.reset!
100125
AcceleratedKernels.rand!
126+
AcceleratedKernels.rand
101127
AcceleratedKernels.randn!
128+
AcceleratedKernels.randn
102129
```

prototype/rand/Project.toml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
[deps]
2+
AMDGPU = "21141c5a-9bdb-4563-92ae-f87d6854732e"
23
AcceleratedKernels = "6a4ca0a5-0e36-4168-a932-d9be78d558f1"
34
BenchmarkTools = "6e4b80f9-dd63-53aa-95a3-0cdb28fa8baf"
45
CUDA = "052768ef-5323-5732-b1bb-66c8b64840ba"
56
Cthulhu = "f68482b8-f384-11e8-15f7-abe071a5a75f"
67
KernelAbstractions = "63c18a36-062a-441e-b654-da1e3ab1ce7c"
7-
PProf = "e4faabce-9ead-11e9-39d9-4379958e3056"
8-
Plots = "91a5bcdd-55d7-5caf-9e0b-520d859cae80"

prototype/rand/plot/Project.toml

Lines changed: 0 additions & 3 deletions
This file was deleted.

prototype/rand/randn.jl

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,4 @@ display(@benchmark run_cuda_randn!($x_cuda))
4848
println("\nAK.randn! Philox benchmark (GPU, CuArray{$TestType})")
4949
display(@benchmark run_ak_randn_gpu!($RNG_PHILOX, $x_philox))
5050

51-
# println("\nAK.randn! benchmark (CPU, Vector{$TestType}, Philox)")
52-
# display(@benchmark run_ak_randn_cpu!($RNG_PHILOX, $x_cpu))
5351

prototype/rand/test_rand.jl

Lines changed: 11 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,13 @@ const RNG_SPLITMIX = AK.CounterRNG(0x12345678; alg=AK.SplitMix64())
1111
const RNG_PHILOX = AK.CounterRNG(0x12345678; alg=AK.Philox())
1212
const RNG_THREEFRY = AK.CounterRNG(0x12345678; alg=AK.Threefry())
1313

14-
x_cuda = CuArray{Float32}(undef, N)
15-
x_splitmix = CuArray{Float32}(undef, N)
16-
x_philox = CuArray{Float32}(undef, N)
17-
x_threefry = CuArray{Float32}(undef, N)
18-
x_cpu = Vector{Float32}(undef, N)
14+
TestType = Float32
15+
16+
x_cuda = CuArray{TestType}(undef, N)
17+
x_splitmix = CuArray{TestType}(undef, N)
18+
x_philox = CuArray{TestType}(undef, N)
19+
x_threefry = CuArray{TestType}(undef, N)
20+
x_cpu = Vector{TestType}(undef, N)
1921

2022

2123
function run_cuda_rand!(x)
@@ -43,32 +45,27 @@ is_unit_interval(v) = all(x -> 0.0f0 <= x <= 1.0f0, v)
4345

4446
# warmup compile
4547
run_cuda_rand!(x_cuda)
46-
# run_ak_rand_gpu!(RNG_SPLITMIX, x_splitmix)
4748
run_ak_rand_gpu!(RNG_PHILOX, x_philox)
4849
run_ak_rand_gpu!(RNG_THREEFRY, x_threefry)
4950
run_ak_rand_cpu!(RNG_SPLITMIX, x_cpu)
5051

5152
@assert is_unit_interval(Array(x_cuda))
52-
# @assert is_unit_interval(Array(x_splitmix))
5353
@assert is_unit_interval(Array(x_philox))
5454
@assert is_unit_interval(Array(x_threefry))
5555
@assert is_unit_interval(x_cpu)
5656

5757
println("N = ", N)
5858
println("CPU threads: ", Threads.nthreads())
5959

60-
println("\nCUDA.rand! benchmark (CuArray{Float32}, in-place)")
60+
println("\nCUDA.rand! benchmark (CuArray{$TestType}, in-place)")
6161
display(@benchmark run_cuda_rand!($x_cuda))
6262

63-
# println("\nAK.rand! SplitMix64 benchmark (GPU, CuArray{Float32})")
64-
# display(@benchmark run_ak_rand_gpu!($RNG_SPLITMIX, $x_splitmix))
65-
66-
println("\nAK.rand! Philox benchmark (GPU, CuArray{Float32})")
63+
println("\nAK.rand! Philox benchmark (GPU, CuArray{$TestType})")
6764
display(@benchmark run_ak_rand_gpu!($RNG_PHILOX, $x_philox))
6865

69-
println("\nAK.rand! Threefry benchmark (GPU, CuArray{Float32})")
66+
println("\nAK.rand! Threefry benchmark (GPU, CuArray{$TestType})")
7067
display(@benchmark run_ak_rand_gpu!($RNG_THREEFRY, $x_threefry))
7168

72-
println("\nAK.rand! benchmark (CPU, Vector{Float32}, SplitMix64)")
69+
println("\nAK.rand! benchmark (CPU, Vector{$TestType}, SplitMix64)")
7370
display(@benchmark run_ak_rand_cpu!($RNG_SPLITMIX, $x_cpu))
7471

src/rand/rand.jl

Lines changed: 18 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -148,13 +148,7 @@ function rand!(
148148
end
149149

150150

151-
function rand!(
152-
v::AbstractArray,
153-
args...;
154-
kwargs...,
155-
)
156-
return rand!(CounterRNG(), v, args...; kwargs...)
157-
end
151+
rand!(v::AbstractArray, args...; kwargs...) = rand!(CounterRNG(), v, args...; kwargs...)
158152

159153

160154
"""
@@ -175,6 +169,11 @@ end
175169
176170
Allocate an array of element type `T` on `backend` with shape `dims`, fill it in-place via
177171
[`rand!`](@ref), and return it.
172+
173+
Convenience overloads:
174+
- `rng` omitted: uses a fresh `CounterRNG()`.
175+
- `backend` omitted: defaults to `CPU_BACKEND`.
176+
- `T` omitted: defaults by backend (`Float64` on CPU backend, `Float32` otherwise).
178177
"""
179178
function rand(
180179
rng::CounterRNG,
@@ -198,21 +197,16 @@ function rand(
198197
end
199198

200199

201-
function rand(
202-
backend::Backend,
203-
::Type{T},
204-
dims::Integer...;
205-
206-
# CPU settings
207-
max_tasks::Int=Threads.nthreads(),
208-
min_elems::Int=1,
209-
prefer_threads::Bool=true,
210-
211-
# GPU settings
212-
block_size::Int=256,
213-
) where T
214-
return rand(
215-
CounterRNG(), backend, T, dims...;
216-
max_tasks, min_elems, prefer_threads, block_size,
217-
)
200+
function rand(rng::CounterRNG, backend::Backend, dims::Integer...; kwargs...)
201+
DefaultScalarType = (backend == CPU_BACKEND) ? Float64 : Float32
202+
rand(rng, backend, DefaultScalarType, dims...; kwargs...)
218203
end
204+
205+
206+
rand(rng::CounterRNG, args...; kwargs...) = rand(rng, CPU_BACKEND, args...; kwargs...)
207+
rand(backend::Backend, args...; kwargs...) = rand(CounterRNG(), backend, args...; kwargs...)
208+
rand(::Type{T}, dims::Integer...; kwargs...) where {T} = rand(CPU_BACKEND, T, dims...; kwargs...)
209+
rand(dims::Integer...; kwargs...) = rand(CPU_BACKEND, dims...; kwargs...)
210+
rand(; kwargs...) = throw(ArgumentError("rand requires at least one dimension"))
211+
212+

src/rand/randn.jl

Lines changed: 19 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -2,76 +2,6 @@ const ALLOWED_RANDN_SCALARS = Union{
22
Float16, Float32, Float64
33
}
44

5-
const OPEN01_MAX_MIDPOINT_INDEX_F32 = UInt32(0x00fffffe)
6-
const OPEN01_MAX_MIDPOINT_INDEX_F64 = UInt64(0x001ffffffffffffe)
7-
const OPEN01_MIDPOINT_SCALE_F32 = ldexp(Float32(1), -24)
8-
const OPEN01_MIDPOINT_SCALE_F64 = ldexp(Float64(1), -53)
9-
10-
11-
12-
13-
#=
14-
The below Float constructions are not duplicates of those in utilities.jl - they are needed to
15-
ensure an interval of (0, 1) as opposed to [0, 1). Achieving this purely logically with midpoint
16-
mapping means we can avoid a check for producing a 0 (which would normally cause a redraw).
17-
Avoiding 0 is essential for Box-Muller due to the logarithm functions.
18-
=#
19-
20-
21-
# Convert random UInt32 bits to Float32 in (0, 1) using midpoint mapping on a 24-bit grid.
22-
@inline function uint32_to_open_unit_float32_midpoint(u::UInt32)::Float32
23-
# `min` keeps the top midpoint below one after Float32 rounding.
24-
k = min(u >> 8, OPEN01_MAX_MIDPOINT_INDEX_F32)
25-
return (Float32(k) + 0.5f0) * OPEN01_MIDPOINT_SCALE_F32
26-
end
27-
28-
29-
# Convert random UInt64 bits to Float64 in (0, 1) using midpoint mapping on a 53-bit grid.
30-
@inline function uint64_to_open_unit_float64_midpoint(u::UInt64)::Float64
31-
# `min` keeps the top midpoint below one after Float64 rounding.
32-
k = min(u >> 11, OPEN01_MAX_MIDPOINT_INDEX_F64)
33-
return (Float64(k) + 0.5) * OPEN01_MIDPOINT_SCALE_F64
34-
end
35-
36-
37-
# Float16 path reuses Float32 midpoint sampling for robust math in Box-Muller.
38-
@inline function rand_open01(
39-
seed::UInt64,
40-
alg::CounterRNGAlgorithm,
41-
counter::UInt64,
42-
::Type{Float16},
43-
)::Float16
44-
return Float16(rand_open01(seed, alg, counter, Float32))
45-
end
46-
47-
48-
@inline function rand_open01(
49-
seed::UInt64,
50-
alg::CounterRNGAlgorithm,
51-
counter::UInt64,
52-
::Type{Float32},
53-
)::Float32
54-
return uint32_to_open_unit_float32_midpoint(rand_uint(seed, alg, counter, UInt32))
55-
end
56-
57-
58-
@inline function rand_open01(
59-
seed::UInt64,
60-
alg::CounterRNGAlgorithm,
61-
counter::UInt64,
62-
::Type{Float64},
63-
)::Float64
64-
return uint64_to_open_unit_float64_midpoint(rand_uint(seed, alg, counter, UInt64))
65-
end
66-
67-
68-
@inline function rand_open01(::UInt64, ::CounterRNGAlgorithm, ::UInt64, ::Type{T}) where {T}
69-
throw(ArgumentError(
70-
"Unsupported open-interval random type $(T). Supported: $(ALLOWED_RANDN_SCALARS)"
71-
))
72-
end
73-
74-
755
@inline function randn_pair(
766
seed::UInt64,
777
alg::CounterRNGAlgorithm,
@@ -90,8 +20,8 @@ end
9020
::Type{Float32},
9121
)::Tuple{Float32, Float32}
9222
u = rand_uint(seed, alg, pair_counter, UInt64)
93-
u1 = uint32_to_open_unit_float32_midpoint(_u32_lo(u))
94-
u2 = uint32_to_open_unit_float32_midpoint(_u32_hi(u))
23+
u1 = _uint32_to_open_unit_float32_midpoint(_u32_lo(u))
24+
u2 = _uint32_to_open_unit_float32_midpoint(_u32_hi(u))
9525
radius = sqrt(-2.0f0 * log(u1))
9626
theta = Float32(2pi) * u2
9727
stheta, ctheta = sincos(theta)
@@ -106,8 +36,8 @@ end
10636
::Type{Float64},
10737
)::Tuple{Float64, Float64}
10838
c0 = pair_counter << 1
109-
u1 = rand_open01(seed, alg, c0, Float64)
110-
u2 = rand_open01(seed, alg, c0 + UInt64(1), Float64)
39+
u1 = rand_float_open01(seed, alg, c0, Float64)
40+
u2 = rand_float_open01(seed, alg, c0 + UInt64(1), Float64)
11141
radius = sqrt(-2.0 * log(u1))
11242
theta = Float64(2pi) * u2
11343
stheta, ctheta = sincos(theta)
@@ -256,13 +186,7 @@ function randn!(
256186
end
257187

258188

259-
function randn!(
260-
v::AbstractArray,
261-
args...;
262-
kwargs...,
263-
)
264-
return randn!(CounterRNG(), v, args...; kwargs...)
265-
end
189+
randn!(v::AbstractArray, args...; kwargs...) = randn!(CounterRNG(), v, args...; kwargs...)
266190

267191

268192
"""
@@ -283,6 +207,11 @@ end
283207
284208
Allocate an array of element type `T` on `backend` with shape `dims`, fill it in-place via
285209
[`randn!`](@ref), and return it.
210+
211+
Convenience overloads:
212+
- `rng` omitted: uses a fresh `CounterRNG()`.
213+
- `backend` omitted: defaults to `CPU_BACKEND`.
214+
- `T` omitted: defaults by backend (`Float64` on CPU backend, `Float32` otherwise).
286215
"""
287216
function randn(
288217
rng::CounterRNG,
@@ -306,21 +235,14 @@ function randn(
306235
end
307236

308237

309-
function randn(
310-
backend::Backend,
311-
::Type{T},
312-
dims::Integer...;
238+
function randn(rng::CounterRNG, backend::Backend, dims::Integer...; kwargs...)
239+
DefaultScalarType = (backend == CPU_BACKEND) ? Float64 : Float32
240+
randn(rng, backend, DefaultScalarType, dims...; kwargs...)
241+
end
313242

314-
# CPU settings
315-
max_tasks::Int=Threads.nthreads(),
316-
min_elems::Int=1,
317-
prefer_threads::Bool=true,
318243

319-
# GPU settings
320-
block_size::Int=256,
321-
) where T
322-
return randn(
323-
CounterRNG(), backend, T, dims...;
324-
max_tasks, min_elems, prefer_threads, block_size,
325-
)
326-
end
244+
randn(rng::CounterRNG, args...; kwargs...) = randn(rng, CPU_BACKEND, args...; kwargs...)
245+
randn(backend::Backend, args...; kwargs...) = randn(CounterRNG(), backend, args...; kwargs...)
246+
randn(::Type{T}, dims::Integer...; kwargs...) where {T} = randn(CPU_BACKEND, T, dims...; kwargs...)
247+
randn(dims::Integer...; kwargs...) = randn(CPU_BACKEND, dims...; kwargs...)
248+
randn(; kwargs...) = throw(ArgumentError("randn requires at least one dimension"))

0 commit comments

Comments
 (0)