Class: Ignis::NvArray
- Inherits:
-
Object
- Object
- Ignis::NvArray
- Defined in:
- lib/nvruby/array.rb
Overview
GPU-aware multi-dimensional array Similar to NumPy ndarray but with CUDA memory backing
Instance Attribute Summary collapse
-
#device_index ⇒ Integer
readonly
Device index.
-
#device_memory ⇒ CUDA::Memory?
readonly
Device memory (nil if on host).
-
#dtype ⇒ Symbol
readonly
Data type (:float32, :float64, :complex64, etc.).
-
#host_memory ⇒ FFI::Pointer?
readonly
Host memory pointer (nil if on device).
-
#location ⇒ :host, :device
readonly
Current memory location.
-
#shape ⇒ Array<Integer>
readonly
Shape of the array.
-
#strides ⇒ Array<Integer>
readonly
Strides in bytes for each dimension.
Class Method Summary collapse
-
.allocate_empty_metadata(shape, dtype) ⇒ NvArray
Create an empty NvArray without allocating memory.
-
.eye(size, dtype: :float32, device: nil) ⇒ NvArray
Create identity matrix.
-
.from_array(data, dtype: :float32, device: nil) ⇒ NvArray
Create array from Ruby array.
-
.from_device_ptr(ptr, shape:, dtype: :float32, take_ownership: false) ⇒ NvArray
Create array from existing device memory.
-
.linspace(start, stop, num, dtype: :float32, device: nil) ⇒ NvArray
Create array with evenly spaced values.
-
.ones(shape, dtype: :float32, device: nil) ⇒ NvArray
Create array filled with ones.
-
.zeros(shape, dtype: :float32, device: nil) ⇒ NvArray
Create array filled with zeros.
Instance Method Summary collapse
-
#contiguous ⇒ NvArray
Create a contiguous copy.
-
#contiguous? ⇒ Boolean
Check if memory layout is contiguous.
-
#device_ffi_ptr ⇒ FFI::Pointer
Get device pointer wrapped as an FFI::Pointer for FFI-bound CUDA-X library calls (cuBLAS/cuSOLVER/cuFFT/cuRAND/cuSPARSE), which cannot accept the Fiddle::Pointer returned by #device_ptr.
-
#device_ptr ⇒ Fiddle::Pointer
Get device pointer for CUDA operations.
-
#dup ⇒ NvArray
Duplicate the array.
-
#flatten ⇒ Array
Get flat data as Ruby array.
-
#free! ⇒ void
Free all memory.
-
#host_ptr ⇒ FFI::Pointer
Get host pointer.
-
#initialize(shape:, dtype: :float32, device: nil, data: nil) ⇒ NvArray
constructor
Create a new NvArray.
-
#inspect ⇒ String
Detailed inspection.
-
#itemsize ⇒ Integer
Size of each element in bytes.
-
#nbytes ⇒ Integer
Total size in bytes.
-
#ndim ⇒ Integer
Number of dimensions.
-
#on_device? ⇒ Boolean
Check if data is on device.
-
#on_host? ⇒ Boolean
Check if data is on host.
-
#reshape(new_shape) ⇒ NvArray
Reshape the array.
-
#size ⇒ Integer
Total number of elements.
-
#to_a ⇒ Array
Get data as Ruby array (copies to host if needed).
-
#to_device(device: nil, stream: nil) ⇒ self
Transfer data to GPU.
-
#to_host(stream: nil) ⇒ self
Transfer data to host.
-
#to_s ⇒ String
String representation.
-
#transpose(axes: nil) ⇒ NvArray
Transpose the array.
-
#zero!(stream: nil) ⇒ self
Zero out the array.
Constructor Details
#initialize(shape:, dtype: :float32, device: nil, data: nil) ⇒ NvArray
Create a new NvArray
33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 |
# File 'lib/nvruby/array.rb', line 33 def initialize(shape:, dtype: :float32, device: nil, data: nil) @shape = normalize_shape(shape) @dtype = DType.validate!(dtype) @device_index = device || Ignis.configuration.default_device @strides = compute_strides @size_bytes = compute_size_bytes @device_memory = nil @host_memory = nil if data initialize_with_data(data) else allocate_memory(device ? :device : :host) end end |
Instance Attribute Details
#device_index ⇒ Integer (readonly)
Returns Device index.
26 27 28 |
# File 'lib/nvruby/array.rb', line 26 def device_index @device_index end |
#device_memory ⇒ CUDA::Memory? (readonly)
Returns Device memory (nil if on host).
17 18 19 |
# File 'lib/nvruby/array.rb', line 17 def device_memory @device_memory end |
#dtype ⇒ Symbol (readonly)
Returns Data type (:float32, :float64, :complex64, etc.).
11 12 13 |
# File 'lib/nvruby/array.rb', line 11 def dtype @dtype end |
#host_memory ⇒ FFI::Pointer? (readonly)
Returns Host memory pointer (nil if on device).
20 21 22 |
# File 'lib/nvruby/array.rb', line 20 def host_memory @host_memory end |
#location ⇒ :host, :device (readonly)
Returns Current memory location.
23 24 25 |
# File 'lib/nvruby/array.rb', line 23 def location @location end |
#shape ⇒ Array<Integer> (readonly)
Returns Shape of the array.
8 9 10 |
# File 'lib/nvruby/array.rb', line 8 def shape @shape end |
#strides ⇒ Array<Integer> (readonly)
Returns Strides in bytes for each dimension.
14 15 16 |
# File 'lib/nvruby/array.rb', line 14 def strides @strides end |
Class Method Details
.allocate_empty_metadata(shape, dtype) ⇒ NvArray
Create an empty NvArray without allocating memory
361 362 363 364 365 366 367 368 369 |
# File 'lib/nvruby/array.rb', line 361 def (shape, dtype) # We use allocate to avoid calling initialize which tries to allocate memory arr = allocate arr.instance_variable_set(:@shape, Array(shape).map(&:to_i)) arr.instance_variable_set(:@dtype, DType.validate!(dtype)) arr.instance_variable_set(:@strides, arr.send(:compute_strides)) arr.instance_variable_set(:@size_bytes, arr.send(:compute_size_bytes)) arr end |
.eye(size, dtype: :float32, device: nil) ⇒ NvArray
Create identity matrix
411 412 413 414 |
# File 'lib/nvruby/array.rb', line 411 def eye(size, dtype: :float32, device: nil) data = Array.new(size * size) { |i| i / size == i % size ? 1.0 : 0.0 } new(shape: [size, size], dtype: dtype, device: device, data: data) end |
.from_array(data, dtype: :float32, device: nil) ⇒ NvArray
Create array from Ruby array
400 401 402 403 404 |
# File 'lib/nvruby/array.rb', line 400 def from_array(data, dtype: :float32, device: nil) shape = infer_shape(data) flat = flatten_nested(data) new(shape: shape, dtype: dtype, device: device, data: flat) end |
.from_device_ptr(ptr, shape:, dtype: :float32, take_ownership: false) ⇒ NvArray
Create array from existing device memory
332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 |
# File 'lib/nvruby/array.rb', line 332 def from_device_ptr(ptr, shape:, dtype: :float32, take_ownership: false) arr = (shape, dtype) if ptr.is_a?(CUDA::Memory) # If it's already a Memory object, we can use it directly # If we don't take ownership, we might need a non-owning version if take_ownership arr.instance_variable_set(:@device_memory, ptr) else # Create a non-owning wrapper for the same pointer wrapper = CUDA::Memory.new(arr.nbytes, device: ptr.device_index, ptr: ptr.device_ptr, owned: false) arr.instance_variable_set(:@device_memory, wrapper) end else # It's a raw FFI::Pointer # We wrap it in a Memory object wrapper = CUDA::Memory.new(arr.nbytes, ptr: ptr, owned: take_ownership) arr.instance_variable_set(:@device_memory, wrapper) end arr.instance_variable_set(:@location, :device) arr.instance_variable_set(:@device_index, arr.device_memory.device_index) arr end |
.linspace(start, stop, num, dtype: :float32, device: nil) ⇒ NvArray
Create array with evenly spaced values
320 321 322 323 324 |
# File 'lib/nvruby/array.rb', line 320 def linspace(start, stop, num, dtype: :float32, device: nil) step = (stop - start).to_f / (num - 1) data = (0...num).map { |i| start + step * i } new(shape: [num], dtype: dtype, device: device, data: data) end |
.ones(shape, dtype: :float32, device: nil) ⇒ NvArray
Create array filled with ones
307 308 309 310 311 |
# File 'lib/nvruby/array.rb', line 307 def ones(shape, dtype: :float32, device: nil) size = Array(shape).reduce(1, :*) data = Array.new(size, 1.0) new(shape: shape, dtype: dtype, device: device, data: data) end |
.zeros(shape, dtype: :float32, device: nil) ⇒ NvArray
Create array filled with zeros
296 297 298 299 300 |
# File 'lib/nvruby/array.rb', line 296 def zeros(shape, dtype: :float32, device: nil) arr = new(shape: shape, dtype: dtype, device: device) arr.zero! arr end |
Instance Method Details
#contiguous ⇒ NvArray
Create a contiguous copy
208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 |
# File 'lib/nvruby/array.rb', line 208 def contiguous return self if contiguous? # Create new array and copy data result = NvArray.new(shape: @shape, dtype: @dtype, device: on_device? ? @device_index : nil) if on_device? # Device-to-device copy result.device_memory.copy_from_device(@device_memory) else # Copy host data result.host_memory.put_bytes(0, @host_memory.get_bytes(0, @size_bytes)) end result end |
#contiguous? ⇒ Boolean
Check if memory layout is contiguous
227 228 229 230 231 232 233 234 235 236 |
# File 'lib/nvruby/array.rb', line 227 def contiguous? expected = itemsize @strides.reverse.each_with_index do |stride, i| dim = @shape[ndim - 1 - i] return false unless stride == expected || dim == 1 expected *= dim end true end |
#device_ffi_ptr ⇒ FFI::Pointer
Get device pointer wrapped as an FFI::Pointer for FFI-bound CUDA-X library calls (cuBLAS/cuSOLVER/cuFFT/cuRAND/cuSPARSE), which cannot accept the Fiddle::Pointer returned by #device_ptr.
136 137 138 139 |
# File 'lib/nvruby/array.rb', line 136 def device_ffi_ptr ensure_device_data! @device_memory.ffi_ptr end |
#device_ptr ⇒ Fiddle::Pointer
Get device pointer for CUDA operations
126 127 128 129 |
# File 'lib/nvruby/array.rb', line 126 def device_ptr ensure_device_data! @device_memory.device_ptr end |
#dup ⇒ NvArray
Duplicate the array
240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 |
# File 'lib/nvruby/array.rb', line 240 def dup result = NvArray.new(shape: @shape.dup, dtype: @dtype, device: on_device? ? @device_index : nil) if on_device? result.instance_variable_set(:@device_memory, CUDA::Memory.new(@size_bytes, device: @device_index)) result.device_memory.copy_from_device(@device_memory) result.instance_variable_set(:@location, :device) else new_host = FFI::MemoryPointer.new(:uint8, @size_bytes) new_host.put_bytes(0, @host_memory.get_bytes(0, @size_bytes)) result.instance_variable_set(:@host_memory, new_host) result.instance_variable_set(:@location, :host) end result end |
#flatten ⇒ Array
Get flat data as Ruby array
161 162 163 164 165 |
# File 'lib/nvruby/array.rb', line 161 def flatten synchronize_if_needed ensure_host_data! read_flat_data end |
#free! ⇒ void
This method returns an undefined value.
Free all memory
271 272 273 274 275 276 |
# File 'lib/nvruby/array.rb', line 271 def free! @device_memory&.free! @device_memory = nil @host_memory = nil @location = nil end |
#host_ptr ⇒ FFI::Pointer
Get host pointer
144 145 146 147 |
# File 'lib/nvruby/array.rb', line 144 def host_ptr ensure_host_data! @host_memory end |
#inspect ⇒ String
Returns Detailed inspection.
285 286 287 288 |
# File 'lib/nvruby/array.rb', line 285 def inspect "#<Ignis::NvArray:#{object_id} shape=#{@shape} dtype=#{@dtype} " \ "location=#{@location} device=#{@device_index} bytes=#{@size_bytes}>" end |
#itemsize ⇒ Integer
Returns Size of each element in bytes.
66 67 68 |
# File 'lib/nvruby/array.rb', line 66 def itemsize DType.byte_size(@dtype) end |
#nbytes ⇒ Integer
Returns Total size in bytes.
61 62 63 |
# File 'lib/nvruby/array.rb', line 61 def nbytes @size_bytes end |
#ndim ⇒ Integer
Returns Number of dimensions.
56 57 58 |
# File 'lib/nvruby/array.rb', line 56 def ndim @shape.size end |
#on_device? ⇒ Boolean
Check if data is on device
72 73 74 |
# File 'lib/nvruby/array.rb', line 72 def on_device? @location == :device end |
#on_host? ⇒ Boolean
Check if data is on host
78 79 80 |
# File 'lib/nvruby/array.rb', line 78 def on_host? @location == :host end |
#reshape(new_shape) ⇒ NvArray
Reshape the array
170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 |
# File 'lib/nvruby/array.rb', line 170 def reshape(new_shape) new_shape = normalize_shape(new_shape) # Handle -1 in shape if new_shape.include?(-1) neg_idx = new_shape.index(-1) other_size = new_shape.reject { |d| d == -1 }.reduce(1, :*) new_shape[neg_idx] = size / other_size end raise DimensionError, "Cannot reshape array of size #{size} to #{new_shape}" unless new_shape.reduce(1, :*) == size # Create new array with same memory result = dup result.instance_variable_set(:@shape, new_shape) result.instance_variable_set(:@strides, result.send(:compute_strides)) result end |
#size ⇒ Integer
Returns Total number of elements.
51 52 53 |
# File 'lib/nvruby/array.rb', line 51 def size @shape.reduce(1, :*) end |
#to_a ⇒ Array
Get data as Ruby array (copies to host if needed)
151 152 153 154 155 156 157 |
# File 'lib/nvruby/array.rb', line 151 def to_a synchronize_if_needed ensure_host_data! flat_data = read_flat_data reshape_to_nested(flat_data, @shape) end |
#to_device(device: nil, stream: nil) ⇒ self
Transfer data to GPU
86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 |
# File 'lib/nvruby/array.rb', line 86 def to_device(device: nil, stream: nil) return self if on_device? && (device.nil? || device == @device_index) target_device = device || @device_index ensure_host_data! @device_memory = CUDA::Memory.new(@size_bytes, device: target_device) @device_memory.copy_from_host(@host_memory, stream: stream) @device_index = target_device @location = :device # Free host memory if not needed @host_memory = nil unless Ignis.configuration.with_lock { false } # Keep host copy option self end |
#to_host(stream: nil) ⇒ self
Transfer data to host
108 109 110 111 112 113 114 115 116 117 118 119 120 121 |
# File 'lib/nvruby/array.rb', line 108 def to_host(stream: nil) return self if on_host? ensure_device_data! @host_memory = FFI::MemoryPointer.new(:uint8, @size_bytes) @device_memory.copy_to_host(host_buffer: @host_memory, stream: stream) @location = :host @device_memory.free! @device_memory = nil self end |
#to_s ⇒ String
Returns String representation.
279 280 281 282 |
# File 'lib/nvruby/array.rb', line 279 def to_s loc = on_device? ? "device:#{@device_index}" : "host" "NvArray(shape=#{@shape}, dtype=#{@dtype}, #{loc})" end |
#transpose(axes: nil) ⇒ NvArray
Transpose the array
192 193 194 195 196 197 198 199 200 201 202 203 204 |
# File 'lib/nvruby/array.rb', line 192 def transpose(axes: nil) axes ||= (0...ndim).to_a.reverse raise DimensionError, "Invalid axes for transpose" unless axes.sort == (0...ndim).to_a new_shape = axes.map { |ax| @shape[ax] } new_strides = axes.map { |ax| @strides[ax] } result = dup result.instance_variable_set(:@shape, new_shape) result.instance_variable_set(:@strides, new_strides) result end |
#zero!(stream: nil) ⇒ self
Zero out the array
260 261 262 263 264 265 266 267 |
# File 'lib/nvruby/array.rb', line 260 def zero!(stream: nil) if on_device? @device_memory.zero!(stream: stream) else @host_memory.clear end self end |