ignis-collective 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/README.md +7 -0
- data/lib/ignis-collective.rb +9 -0
- data/lib/nvruby/collective/algorithms/double_binary_tree.rb +364 -0
- data/lib/nvruby/collective/algorithms/pipeliner.rb +222 -0
- data/lib/nvruby/collective/algorithms/reduction_ops.rb +168 -0
- data/lib/nvruby/collective/algorithms/ring.rb +421 -0
- data/lib/nvruby/collective/algorithms/topology_router.rb +284 -0
- data/lib/nvruby/collective/algorithms/tree.rb +291 -0
- data/lib/nvruby/collective/array_ops.rb +240 -0
- data/lib/nvruby/collective/communicator.rb +633 -0
- data/lib/nvruby/collective/communicator_healer.rb +276 -0
- data/lib/nvruby/collective/device_manager.rb +216 -0
- data/lib/nvruby/collective/dynamic_optimizer.rb +308 -0
- data/lib/nvruby/collective/health_monitor.rb +333 -0
- data/lib/nvruby/collective/net/nd_adapter.rb +450 -0
- data/lib/nvruby/collective/net/nd_bindings.rb +166 -0
- data/lib/nvruby/collective/net/rdma_transport.rb +366 -0
- data/lib/nvruby/collective/nvarray_adapter.rb +230 -0
- data/lib/nvruby/collective/p2p_bindings.rb +121 -0
- data/lib/nvruby/collective/resilient_transport.rb +296 -0
- data/lib/nvruby/collective/topology.rb +347 -0
- data/lib/nvruby/collective/transport/base.rb +138 -0
- data/lib/nvruby/collective/transport/host_staged_transport.rb +217 -0
- data/lib/nvruby/collective/transport/ipc_transport.rb +187 -0
- data/lib/nvruby/collective/transport/p2p_transport.rb +157 -0
- data/lib/nvruby/collective/transport/rdma_transports.rb +213 -0
- data/lib/nvruby/collective/transport/rio_transport.rb +405 -0
- data/lib/nvruby/collective/transport/tcp_transport.rb +290 -0
- data/lib/nvruby/collective/transport/vmm_ipc_structs.rb +189 -0
- data/lib/nvruby/collective/transport/vmm_ipc_transport.rb +266 -0
- data/lib/nvruby/collective/transport_selector.rb +200 -0
- data/lib/nvruby/collective/vmm_bindings.rb +212 -0
- data/lib/nvruby/collective.rb +156 -0
- metadata +92 -0
|
@@ -0,0 +1,450 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "nd_bindings"
|
|
4
|
+
|
|
5
|
+
module Ignis
|
|
6
|
+
module Collective
|
|
7
|
+
module NetworkDirect
|
|
8
|
+
# RDMA Adapter wrapper for NetworkDirect
|
|
9
|
+
#
|
|
10
|
+
# Represents a single RDMA-capable NIC (e.g., Mellanox ConnectX-5)
|
|
11
|
+
# and provides methods for creating queue pairs and memory regions.
|
|
12
|
+
class Adapter
|
|
13
|
+
# @return [String] Adapter identifier
|
|
14
|
+
attr_reader :adapter_id
|
|
15
|
+
|
|
16
|
+
# @return [FFI::Pointer] Native adapter handle
|
|
17
|
+
attr_reader :handle
|
|
18
|
+
|
|
19
|
+
# @return [Bindings::ND2AdapterInfo] Adapter capabilities
|
|
20
|
+
attr_reader :info
|
|
21
|
+
|
|
22
|
+
# Initialize adapter wrapper
|
|
23
|
+
# @param handle [FFI::Pointer] Native IND2Adapter handle
|
|
24
|
+
# @param adapter_id [String] Adapter identifier
|
|
25
|
+
def initialize(handle, adapter_id)
|
|
26
|
+
@handle = handle
|
|
27
|
+
@adapter_id = adapter_id
|
|
28
|
+
@info = nil
|
|
29
|
+
@completion_queues = []
|
|
30
|
+
@queue_pairs = []
|
|
31
|
+
@memory_regions = []
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Query adapter capabilities
|
|
35
|
+
# @return [Hash] Adapter capabilities
|
|
36
|
+
def query
|
|
37
|
+
return @capabilities if @capabilities
|
|
38
|
+
|
|
39
|
+
@info = Bindings::ND2AdapterInfo.new
|
|
40
|
+
# In real implementation, call IND2Adapter::Query via COM interop
|
|
41
|
+
|
|
42
|
+
@capabilities = {
|
|
43
|
+
vendor_id: @info[:VendorId],
|
|
44
|
+
device_id: @info[:DeviceId],
|
|
45
|
+
max_registration_size: @info[:MaxRegistrationSize],
|
|
46
|
+
max_sge_count: @info[:MaxSgeCount],
|
|
47
|
+
max_send_queue_depth: @info[:MaxSendQueueDepth],
|
|
48
|
+
max_recv_queue_depth: @info[:MaxRecvQueueDepth],
|
|
49
|
+
max_cq_depth: @info[:MaxCqDepth],
|
|
50
|
+
max_inline_data: @info[:MaxInlineData],
|
|
51
|
+
max_outbound_read_limit: @info[:MaxOutboundReadLimit],
|
|
52
|
+
max_inbound_read_limit: @info[:MaxInboundReadLimit]
|
|
53
|
+
}
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Create a completion queue
|
|
57
|
+
# @param depth [Integer] Number of entries
|
|
58
|
+
# @return [CompletionQueue] New completion queue
|
|
59
|
+
def create_completion_queue(depth: 256)
|
|
60
|
+
cq = CompletionQueue.new(self, depth)
|
|
61
|
+
cq.create!
|
|
62
|
+
@completion_queues << cq
|
|
63
|
+
cq
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Create a queue pair
|
|
67
|
+
# @param send_cq [CompletionQueue] Send completion queue
|
|
68
|
+
# @param recv_cq [CompletionQueue] Receive completion queue
|
|
69
|
+
# @param send_depth [Integer] Send queue depth
|
|
70
|
+
# @param recv_depth [Integer] Receive queue depth
|
|
71
|
+
# @param sge_count [Integer] Max SGE per work request
|
|
72
|
+
# @return [QueuePair] New queue pair
|
|
73
|
+
def create_queue_pair(send_cq:, recv_cq:, send_depth: 64, recv_depth: 64, sge_count: 1)
|
|
74
|
+
qp = QueuePair.new(
|
|
75
|
+
adapter: self,
|
|
76
|
+
send_cq: send_cq,
|
|
77
|
+
recv_cq: recv_cq,
|
|
78
|
+
send_depth: send_depth,
|
|
79
|
+
recv_depth: recv_depth,
|
|
80
|
+
sge_count: sge_count
|
|
81
|
+
)
|
|
82
|
+
qp.create!
|
|
83
|
+
@queue_pairs << qp
|
|
84
|
+
qp
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# Register a memory region for RDMA
|
|
88
|
+
# @param buffer [FFI::Pointer] Buffer to register
|
|
89
|
+
# @param size [Integer] Buffer size
|
|
90
|
+
# @param flags [Integer] Registration flags
|
|
91
|
+
# @return [MemoryRegion] Registered memory region
|
|
92
|
+
def register_memory(buffer, size, flags: 0)
|
|
93
|
+
mr = MemoryRegion.new(self, buffer, size, flags)
|
|
94
|
+
mr.register!
|
|
95
|
+
@memory_regions << mr
|
|
96
|
+
mr
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# Create a connector for connection establishment
|
|
100
|
+
# @return [Connector] New connector
|
|
101
|
+
def create_connector
|
|
102
|
+
Connector.new(self)
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# Release all resources
|
|
106
|
+
# @return [void]
|
|
107
|
+
def close!
|
|
108
|
+
@queue_pairs.each(&:close!)
|
|
109
|
+
@completion_queues.each(&:close!)
|
|
110
|
+
@memory_regions.each(&:deregister!)
|
|
111
|
+
|
|
112
|
+
@queue_pairs.clear
|
|
113
|
+
@completion_queues.clear
|
|
114
|
+
@memory_regions.clear
|
|
115
|
+
|
|
116
|
+
# Release native handle
|
|
117
|
+
# In real implementation: IND2Adapter::Release
|
|
118
|
+
@handle = nil
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
# Completion Queue for RDMA operation completions
|
|
123
|
+
class CompletionQueue
|
|
124
|
+
# @return [Adapter] Parent adapter
|
|
125
|
+
attr_reader :adapter
|
|
126
|
+
|
|
127
|
+
# @return [Integer] Queue depth
|
|
128
|
+
attr_reader :depth
|
|
129
|
+
|
|
130
|
+
# @return [FFI::Pointer] Native CQ handle
|
|
131
|
+
attr_reader :handle
|
|
132
|
+
|
|
133
|
+
def initialize(adapter, depth)
|
|
134
|
+
@adapter = adapter
|
|
135
|
+
@depth = depth
|
|
136
|
+
@handle = nil
|
|
137
|
+
@created = false
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
# Create the completion queue
|
|
141
|
+
# @return [void]
|
|
142
|
+
def create!
|
|
143
|
+
return if @created
|
|
144
|
+
|
|
145
|
+
# In real implementation: IND2Adapter::CreateCompletionQueue
|
|
146
|
+
@handle = FFI::Pointer.new(:void, 0x12345678) # Placeholder
|
|
147
|
+
@created = true
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
# Poll for completions
|
|
151
|
+
# @param max_results [Integer] Max completions to retrieve
|
|
152
|
+
# @return [Array<Hash>] Completion results
|
|
153
|
+
def poll(max_results: 16)
|
|
154
|
+
return [] unless @created
|
|
155
|
+
|
|
156
|
+
# Allocate result buffer
|
|
157
|
+
results_ptr = FFI::MemoryPointer.new(Bindings::ND2Result, max_results)
|
|
158
|
+
count_ptr = FFI::MemoryPointer.new(:uint32)
|
|
159
|
+
|
|
160
|
+
# In real implementation: IND2CompletionQueue::GetResults
|
|
161
|
+
# Returns array of completion results
|
|
162
|
+
|
|
163
|
+
# Parse results
|
|
164
|
+
results = []
|
|
165
|
+
count = count_ptr.read_uint32
|
|
166
|
+
count.times do |i|
|
|
167
|
+
result = Bindings::ND2Result.new(results_ptr + i * Bindings::ND2Result.size)
|
|
168
|
+
results << {
|
|
169
|
+
status: result[:Status],
|
|
170
|
+
bytes_transferred: result[:BytesTransferred],
|
|
171
|
+
context: result[:RequestContext],
|
|
172
|
+
type: result[:RequestType]
|
|
173
|
+
}
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
results
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
# Wait for completion with timeout
|
|
180
|
+
# @param timeout_ms [Integer] Timeout in milliseconds
|
|
181
|
+
# @return [Boolean] True if completion available
|
|
182
|
+
def wait(timeout_ms: 1000)
|
|
183
|
+
return false unless @created
|
|
184
|
+
|
|
185
|
+
# In real implementation: IND2CompletionQueue::Notify with INFINITE or timeout
|
|
186
|
+
true
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
# Close the completion queue
|
|
190
|
+
# @return [void]
|
|
191
|
+
def close!
|
|
192
|
+
return unless @created
|
|
193
|
+
|
|
194
|
+
# In real implementation: IND2CompletionQueue::Release
|
|
195
|
+
@handle = nil
|
|
196
|
+
@created = false
|
|
197
|
+
end
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
# Queue Pair for RDMA send/receive operations
|
|
201
|
+
class QueuePair
|
|
202
|
+
# States
|
|
203
|
+
STATE_INIT = :init
|
|
204
|
+
STATE_READY = :ready
|
|
205
|
+
STATE_CONNECTED = :connected
|
|
206
|
+
STATE_ERROR = :error
|
|
207
|
+
|
|
208
|
+
# @return [Adapter] Parent adapter
|
|
209
|
+
attr_reader :adapter
|
|
210
|
+
|
|
211
|
+
# @return [Symbol] Current state
|
|
212
|
+
attr_reader :state
|
|
213
|
+
|
|
214
|
+
# @return [FFI::Pointer] Native QP handle
|
|
215
|
+
attr_reader :handle
|
|
216
|
+
|
|
217
|
+
def initialize(adapter:, send_cq:, recv_cq:, send_depth:, recv_depth:, sge_count:)
|
|
218
|
+
@adapter = adapter
|
|
219
|
+
@send_cq = send_cq
|
|
220
|
+
@recv_cq = recv_cq
|
|
221
|
+
@send_depth = send_depth
|
|
222
|
+
@recv_depth = recv_depth
|
|
223
|
+
@sge_count = sge_count
|
|
224
|
+
@handle = nil
|
|
225
|
+
@state = STATE_INIT
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
# Create the queue pair
|
|
229
|
+
# @return [void]
|
|
230
|
+
def create!
|
|
231
|
+
return if @state != STATE_INIT
|
|
232
|
+
|
|
233
|
+
# In real implementation: IND2Adapter::CreateQueuePair
|
|
234
|
+
@handle = FFI::Pointer.new(:void, 0x87654321) # Placeholder
|
|
235
|
+
@state = STATE_READY
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
# Post a send work request
|
|
239
|
+
# @param sge_list [Array<Hash>] Scatter-gather entries
|
|
240
|
+
# @param context [FFI::Pointer] User context
|
|
241
|
+
# @param flags [Integer] Send flags (inline, signaled, etc.)
|
|
242
|
+
# @return [void]
|
|
243
|
+
def post_send(sge_list:, context: nil, flags: 0)
|
|
244
|
+
raise RDMAError, "QP not ready" unless @state == STATE_CONNECTED
|
|
245
|
+
|
|
246
|
+
# Build SGE array
|
|
247
|
+
sge_count = sge_list.size
|
|
248
|
+
sge_ptr = FFI::MemoryPointer.new(Bindings::ND2Sge, sge_count)
|
|
249
|
+
|
|
250
|
+
sge_list.each_with_index do |sge, i|
|
|
251
|
+
entry = Bindings::ND2Sge.new(sge_ptr + i * Bindings::ND2Sge.size)
|
|
252
|
+
entry[:Buffer] = sge[:buffer]
|
|
253
|
+
entry[:BufferLength] = sge[:length]
|
|
254
|
+
entry[:MemoryRegionToken] = sge[:token]
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
# In real implementation: IND2QueuePair::Send
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
# Post a receive work request
|
|
261
|
+
# @param sge_list [Array<Hash>] Scatter-gather entries
|
|
262
|
+
# @param context [FFI::Pointer] User context
|
|
263
|
+
# @return [void]
|
|
264
|
+
def post_receive(sge_list:, context: nil)
|
|
265
|
+
raise RDMAError, "QP not ready" unless @state == STATE_READY || @state == STATE_CONNECTED
|
|
266
|
+
|
|
267
|
+
# Build SGE array (similar to post_send)
|
|
268
|
+
# In real implementation: IND2QueuePair::Receive
|
|
269
|
+
end
|
|
270
|
+
|
|
271
|
+
# RDMA Write operation (one-sided)
|
|
272
|
+
# @param remote_address [Integer] Remote buffer address
|
|
273
|
+
# @param remote_token [Integer] Remote memory token
|
|
274
|
+
# @param sge_list [Array<Hash>] Local scatter-gather entries
|
|
275
|
+
# @param context [FFI::Pointer] User context
|
|
276
|
+
# @return [void]
|
|
277
|
+
def rdma_write(remote_address:, remote_token:, sge_list:, context: nil)
|
|
278
|
+
raise RDMAError, "QP not connected" unless @state == STATE_CONNECTED
|
|
279
|
+
|
|
280
|
+
# In real implementation: IND2QueuePair::Write
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
# RDMA Read operation (one-sided)
|
|
284
|
+
# @param remote_address [Integer] Remote buffer address
|
|
285
|
+
# @param remote_token [Integer] Remote memory token
|
|
286
|
+
# @param sge_list [Array<Hash>] Local scatter-gather entries
|
|
287
|
+
# @param context [FFI::Pointer] User context
|
|
288
|
+
# @return [void]
|
|
289
|
+
def rdma_read(remote_address:, remote_token:, sge_list:, context: nil)
|
|
290
|
+
raise RDMAError, "QP not connected" unless @state == STATE_CONNECTED
|
|
291
|
+
|
|
292
|
+
# In real implementation: IND2QueuePair::Read
|
|
293
|
+
end
|
|
294
|
+
|
|
295
|
+
# Transition to connected state
|
|
296
|
+
# @return [void]
|
|
297
|
+
def set_connected!
|
|
298
|
+
@state = STATE_CONNECTED
|
|
299
|
+
end
|
|
300
|
+
|
|
301
|
+
# Close the queue pair
|
|
302
|
+
# @return [void]
|
|
303
|
+
def close!
|
|
304
|
+
# In real implementation: IND2QueuePair::Release
|
|
305
|
+
@handle = nil
|
|
306
|
+
@state = STATE_ERROR
|
|
307
|
+
end
|
|
308
|
+
end
|
|
309
|
+
|
|
310
|
+
# Memory Region for RDMA registration
|
|
311
|
+
class MemoryRegion
|
|
312
|
+
# @return [FFI::Pointer] Buffer pointer
|
|
313
|
+
attr_reader :buffer
|
|
314
|
+
|
|
315
|
+
# @return [Integer] Buffer size
|
|
316
|
+
attr_reader :size
|
|
317
|
+
|
|
318
|
+
# @return [Integer] Memory region token (for remote access)
|
|
319
|
+
attr_reader :token
|
|
320
|
+
|
|
321
|
+
# @return [FFI::Pointer] Native MR handle
|
|
322
|
+
attr_reader :handle
|
|
323
|
+
|
|
324
|
+
def initialize(adapter, buffer, size, flags)
|
|
325
|
+
@adapter = adapter
|
|
326
|
+
@buffer = buffer
|
|
327
|
+
@size = size
|
|
328
|
+
@flags = flags
|
|
329
|
+
@handle = nil
|
|
330
|
+
@token = nil
|
|
331
|
+
@registered = false
|
|
332
|
+
end
|
|
333
|
+
|
|
334
|
+
# Register the memory region
|
|
335
|
+
# @return [void]
|
|
336
|
+
def register!
|
|
337
|
+
return if @registered
|
|
338
|
+
|
|
339
|
+
# In real implementation: IND2Adapter::CreateMemoryRegion + Register
|
|
340
|
+
@handle = FFI::Pointer.new(:void, 0xABCDEF00) # Placeholder
|
|
341
|
+
@token = rand(0xFFFFFFFF) # Would be from GetRemoteToken
|
|
342
|
+
@registered = true
|
|
343
|
+
end
|
|
344
|
+
|
|
345
|
+
# Get remote access token for RDMA write/read
|
|
346
|
+
# @return [Hash] Remote access info
|
|
347
|
+
def remote_access_info
|
|
348
|
+
raise RDMAError, "Memory not registered" unless @registered
|
|
349
|
+
|
|
350
|
+
{
|
|
351
|
+
address: @buffer.address,
|
|
352
|
+
token: @token,
|
|
353
|
+
size: @size
|
|
354
|
+
}
|
|
355
|
+
end
|
|
356
|
+
|
|
357
|
+
# Deregister the memory region
|
|
358
|
+
# @return [void]
|
|
359
|
+
def deregister!
|
|
360
|
+
return unless @registered
|
|
361
|
+
|
|
362
|
+
# In real implementation: IND2MemoryRegion::Deregister + Release
|
|
363
|
+
@handle = nil
|
|
364
|
+
@token = nil
|
|
365
|
+
@registered = false
|
|
366
|
+
end
|
|
367
|
+
end
|
|
368
|
+
|
|
369
|
+
# Connector for RDMA connection establishment
|
|
370
|
+
class Connector
|
|
371
|
+
# @return [Adapter] Parent adapter
|
|
372
|
+
attr_reader :adapter
|
|
373
|
+
|
|
374
|
+
# @return [FFI::Pointer] Native connector handle
|
|
375
|
+
attr_reader :handle
|
|
376
|
+
|
|
377
|
+
def initialize(adapter)
|
|
378
|
+
@adapter = adapter
|
|
379
|
+
@handle = nil
|
|
380
|
+
@bound = false
|
|
381
|
+
@connected = false
|
|
382
|
+
end
|
|
383
|
+
|
|
384
|
+
# Bind to local address
|
|
385
|
+
# @param address [String] Local IP address
|
|
386
|
+
# @param port [Integer] Local port
|
|
387
|
+
# @return [void]
|
|
388
|
+
def bind(address:, port:)
|
|
389
|
+
return if @bound
|
|
390
|
+
|
|
391
|
+
# In real implementation: IND2Connector::Bind
|
|
392
|
+
@local_address = address
|
|
393
|
+
@local_port = port
|
|
394
|
+
@bound = true
|
|
395
|
+
end
|
|
396
|
+
|
|
397
|
+
# Connect to remote peer (client side)
|
|
398
|
+
# @param qp [QueuePair] Queue pair to connect
|
|
399
|
+
# @param remote_address [String] Remote IP address
|
|
400
|
+
# @param remote_port [Integer] Remote port
|
|
401
|
+
# @param private_data [String, nil] Connection private data
|
|
402
|
+
# @return [void]
|
|
403
|
+
def connect(qp:, remote_address:, remote_port:, private_data: nil)
|
|
404
|
+
raise RDMAError, "Not bound" unless @bound
|
|
405
|
+
|
|
406
|
+
# In real implementation: IND2Connector::Connect
|
|
407
|
+
# Then poll for completion
|
|
408
|
+
# Then CompleteConnect
|
|
409
|
+
|
|
410
|
+
@connected = true
|
|
411
|
+
qp.set_connected!
|
|
412
|
+
end
|
|
413
|
+
|
|
414
|
+
# Accept connection (server side)
|
|
415
|
+
# @param qp [QueuePair] Queue pair for the connection
|
|
416
|
+
# @param private_data [String, nil] Response private data
|
|
417
|
+
# @return [void]
|
|
418
|
+
def accept(qp:, private_data: nil)
|
|
419
|
+
raise RDMAError, "Not bound" unless @bound
|
|
420
|
+
|
|
421
|
+
# In real implementation:
|
|
422
|
+
# 1. Listen for incoming connection
|
|
423
|
+
# 2. GetConnectionRequest
|
|
424
|
+
# 3. Accept with QP
|
|
425
|
+
|
|
426
|
+
@connected = true
|
|
427
|
+
qp.set_connected!
|
|
428
|
+
end
|
|
429
|
+
|
|
430
|
+
# Disconnect
|
|
431
|
+
# @return [void]
|
|
432
|
+
def disconnect!
|
|
433
|
+
return unless @connected
|
|
434
|
+
|
|
435
|
+
# In real implementation: IND2Connector::Disconnect
|
|
436
|
+
@connected = false
|
|
437
|
+
end
|
|
438
|
+
|
|
439
|
+
# Close the connector
|
|
440
|
+
# @return [void]
|
|
441
|
+
def close!
|
|
442
|
+
disconnect! if @connected
|
|
443
|
+
# Release handle
|
|
444
|
+
@handle = nil
|
|
445
|
+
@bound = false
|
|
446
|
+
end
|
|
447
|
+
end
|
|
448
|
+
end
|
|
449
|
+
end
|
|
450
|
+
end
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "ffi"
|
|
4
|
+
|
|
5
|
+
module Ignis
|
|
6
|
+
module Collective
|
|
7
|
+
module NetworkDirect
|
|
8
|
+
# Windows NetworkDirect SPI v2 FFI Bindings
|
|
9
|
+
#
|
|
10
|
+
# NetworkDirect is **Windows-native** RDMA API supporting:
|
|
11
|
+
# - InfiniBand
|
|
12
|
+
# - RoCE (RDMA over Converged Ethernet)
|
|
13
|
+
# - iWARP
|
|
14
|
+
#
|
|
15
|
+
# Requires: Mellanox ConnectX NICs with WinOF-2 drivers
|
|
16
|
+
#
|
|
17
|
+
# Key interfaces (COM-based):
|
|
18
|
+
# - IND2Provider: Service provider, opens adapters
|
|
19
|
+
# - IND2Adapter: Hardware adapter, creates queues
|
|
20
|
+
# - IND2CompletionQueue: RDMA completion notifications
|
|
21
|
+
# - IND2QueuePair: Send/Receive queue pair for I/O
|
|
22
|
+
# - IND2MemoryRegion: Registered RDMA memory
|
|
23
|
+
# - IND2Connector: Connection establishment
|
|
24
|
+
module Bindings
|
|
25
|
+
extend FFI::Library
|
|
26
|
+
|
|
27
|
+
# NetworkDirect status codes
|
|
28
|
+
ND_SUCCESS = 0
|
|
29
|
+
ND_TIMEOUT = 0x80070102
|
|
30
|
+
ND_PENDING = 0x80000005
|
|
31
|
+
ND_BUFFER_OVERFLOW = 0x8007006F
|
|
32
|
+
ND_DEVICE_NOT_READY = 0x80070015
|
|
33
|
+
ND_NO_MEMORY = 0x8007000E
|
|
34
|
+
ND_CONNECTION_REFUSED = 0x8007107D
|
|
35
|
+
ND_CONNECTION_ABORTED = 0x80070453
|
|
36
|
+
ND_CONNECTION_INVALID = 0x800710CD
|
|
37
|
+
ND_NOT_SUPPORTED = 0x80070032
|
|
38
|
+
|
|
39
|
+
# Queue pair types
|
|
40
|
+
ND_QP_TYPE_SEND_RECV = 0
|
|
41
|
+
ND_QP_TYPE_RDMA = 1
|
|
42
|
+
|
|
43
|
+
# Work completion status
|
|
44
|
+
ND_CQ_SUCCESS = 0
|
|
45
|
+
ND_CQ_FLUSHED = 1
|
|
46
|
+
ND_CQ_LOCAL_ERROR = 2
|
|
47
|
+
ND_CQ_TRANSPORT_ERROR = 3
|
|
48
|
+
ND_CQ_RNR_ERROR = 4
|
|
49
|
+
|
|
50
|
+
# ND2_RESULT structure for completion queue
|
|
51
|
+
class ND2Result < FFI::Struct
|
|
52
|
+
layout :Status, :uint32, # Completion status
|
|
53
|
+
:BytesTransferred, :uint32,
|
|
54
|
+
:QueuePairContext, :pointer,
|
|
55
|
+
:RequestContext, :pointer,
|
|
56
|
+
:RequestType, :uint32
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# ND2_ADAPTER_INFO structure
|
|
60
|
+
class ND2AdapterInfo < FFI::Struct
|
|
61
|
+
layout :InfoVersion, :uint32,
|
|
62
|
+
:VendorId, :uint32,
|
|
63
|
+
:DeviceId, :uint32,
|
|
64
|
+
:AdapterId, :uint64,
|
|
65
|
+
:MaxRegistrationSize, :size_t,
|
|
66
|
+
:MaxWindowSize, :size_t,
|
|
67
|
+
:MaxSgeCount, :uint32,
|
|
68
|
+
:MaxReadQueueDepth, :uint32,
|
|
69
|
+
:MaxRecvQueueDepth, :uint32,
|
|
70
|
+
:MaxSendQueueDepth, :uint32,
|
|
71
|
+
:MaxCqDepth, :uint32,
|
|
72
|
+
:MaxInlineData, :uint32,
|
|
73
|
+
:MaxOutboundReadLimit, :uint32,
|
|
74
|
+
:MaxInboundReadLimit, :uint32
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# ND2_SGE (Scatter-Gather Entry)
|
|
78
|
+
class ND2Sge < FFI::Struct
|
|
79
|
+
layout :Buffer, :pointer,
|
|
80
|
+
:BufferLength, :uint32,
|
|
81
|
+
:MemoryRegionToken, :uint32
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
@loaded = false
|
|
85
|
+
|
|
86
|
+
def self.ensure_loaded!
|
|
87
|
+
return if @loaded
|
|
88
|
+
|
|
89
|
+
begin
|
|
90
|
+
# NetworkDirect provider DLL (from Mellanox WinOF-2 or similar)
|
|
91
|
+
# Standard locations for RDMA providers
|
|
92
|
+
ffi_lib [
|
|
93
|
+
"nd",
|
|
94
|
+
"NetworkDirect",
|
|
95
|
+
"mlx5nd", # Mellanox ConnectX-5+
|
|
96
|
+
"mlx4nd" # Older Mellanox
|
|
97
|
+
]
|
|
98
|
+
attach_nd_functions!
|
|
99
|
+
@loaded = true
|
|
100
|
+
rescue FFI::NotFoundError => e
|
|
101
|
+
# Not an error if hardware not present
|
|
102
|
+
@loaded = false
|
|
103
|
+
@load_error = e.message
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def self.available?
|
|
108
|
+
ensure_loaded!
|
|
109
|
+
@loaded
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def self.load_error
|
|
113
|
+
@load_error
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def self.attach_nd_functions!
|
|
117
|
+
# Provider enumeration
|
|
118
|
+
attach_function :NdOpenAdapter, [
|
|
119
|
+
:pointer, # const ND2_ADAPTER_ADDRESS* pAddress
|
|
120
|
+
:uint32, # DWORD cbAddressLength
|
|
121
|
+
:pointer # IND2Adapter** ppAdapter (output)
|
|
122
|
+
], :int32
|
|
123
|
+
|
|
124
|
+
# Query adapters
|
|
125
|
+
attach_function :NdQueryAddressList, [
|
|
126
|
+
:uint32, # DWORD Flags
|
|
127
|
+
:pointer, # ND2_ADAPTER_ADDRESS* pAddressList
|
|
128
|
+
:pointer # DWORD* pcbAddressList (in/out)
|
|
129
|
+
], :int32
|
|
130
|
+
|
|
131
|
+
# Provider initialization
|
|
132
|
+
attach_function :NdStartup, [:uint16], :int32 # Version
|
|
133
|
+
attach_function :NdCleanup, [], :int32
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
# Check status and raise on error
|
|
137
|
+
def self.check_status!(status, context = "NetworkDirect operation")
|
|
138
|
+
return if status == ND_SUCCESS
|
|
139
|
+
|
|
140
|
+
error_name = case status
|
|
141
|
+
when ND_TIMEOUT then "ND_TIMEOUT"
|
|
142
|
+
when ND_PENDING then "ND_PENDING"
|
|
143
|
+
when ND_NO_MEMORY then "ND_NO_MEMORY"
|
|
144
|
+
when ND_DEVICE_NOT_READY then "ND_DEVICE_NOT_READY"
|
|
145
|
+
when ND_CONNECTION_REFUSED then "ND_CONNECTION_REFUSED"
|
|
146
|
+
when ND_CONNECTION_ABORTED then "ND_CONNECTION_ABORTED"
|
|
147
|
+
when ND_NOT_SUPPORTED then "ND_NOT_SUPPORTED"
|
|
148
|
+
else "UNKNOWN_ERROR"
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
raise RDMAError.new("#{context}: #{error_name} (0x#{status.to_s(16)})", code: status)
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
# Custom RDMA error class
|
|
156
|
+
class RDMAError < StandardError
|
|
157
|
+
attr_reader :code
|
|
158
|
+
|
|
159
|
+
def initialize(message, code: nil)
|
|
160
|
+
super(message)
|
|
161
|
+
@code = code
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
end
|