mirror of
https://github.com/luxfi/zap.git
synced 2026-10-30 15:04:16 +00:00
Three orthogonal additions, one wire format. v2 package — generic ad-hoc schema dispatch (~/work/lux/zap/v2): - README + IMPOSSIBILITY + BENCH_RESULTS document the v1↔v2 split. - builder/field/iter/list/pool/registry/schema/view + codegen package emit v1-equivalent fast paths from declarative .zap sources. - _compile_fail_test/ + examples/ cover negative and positive cases. - v2_test.go exercises the generic dispatch path. - doc.go in v1 marks v1 deprecated for *new* schema authoring; existing v1 buffers stay parseable by both v1 and v2. bufpool — pooled read slabs (bufpool.go + bufpool_test.go): - Message gains optional *bufRef; Release() returns the slab to its pool when sourced from the pooled read path, GC-managed otherwise. - ParseHeader returns (data, rootOffset) without allocating *Message, used by zapv2 to avoid two allocations per parse. - bench_read_test.go + node_quic_stream_test.go cover both paths. Transport allocator + UMA/RDMA cgo (transport/*): - Transport.Caps()/Buffer interface: capability + slab-typed buffer contract. Bytes() vs DevicePtr() decoupled so UMA and GPUDirect can share the same call surface. - DPDK PMD + GPUDirect RDMA + CUDA UMA real cgo backends gated behind build tags; stub paths remain default on darwin / non-Linux. - transport_test.go covers Caps reporting + Buffer lifecycle on the default transport. LLM.md + go.mod follow the additions.
219 lines
8.0 KiB
Go
219 lines
8.0 KiB
Go
// Copyright (C) 2025-2026, Lux Industries Inc. All rights reserved.
|
|
// See the file LICENSE for licensing terms.
|
|
|
|
// Package transport is the pluggable GPU-aware ZAP transport.
|
|
//
|
|
// Three transports are implemented:
|
|
//
|
|
// default — standard Go net (CPU parsing). Always available.
|
|
// uma — Apple Silicon / NVIDIA Grace unified-memory. cgo + darwin.
|
|
// Network packets land in shared RAM; GPU kernels read them
|
|
// at GPU memory speed without a copy.
|
|
// gpudirect — NVIDIA + Mellanox CX-6/7 (GPUDirect RDMA). cgo + linux.
|
|
// NIC DMA into VRAM, ZAP parsed by GPU kernel, no CPU touch.
|
|
// dpdk — Linux DPDK + GPU-mapped hugepages. cgo + linux.
|
|
// Kernel-bypass packet ingestion, GPU reads from mapped huge-
|
|
// pages. Best for non-Mellanox NICs.
|
|
//
|
|
// All four implement the same Transport interface. Pick() chooses the
|
|
// best available implementation at runtime, honoring a caller-supplied
|
|
// preference and falling through gracefully when a preferred path is
|
|
// unavailable. There is no `gpu` build tag — cgo is the only gate, and
|
|
// the OS gate selects which native transports can compile in.
|
|
package transport
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"log/slog"
|
|
"os"
|
|
"strings"
|
|
"sync"
|
|
"unsafe"
|
|
)
|
|
|
|
// Transport is the wire-level surface multichain gossip uses to ship
|
|
// sealed MultiChainBlocks between validators. The interface is sized for
|
|
// one ZAP message per Send/Recv — batching happens above this layer.
|
|
type Transport interface {
|
|
// Name returns the canonical low-cardinality transport identifier
|
|
// (matches the env-var spelling): "default", "uma", "gpudirect",
|
|
// "dpdk". Used in metrics labels and log lines.
|
|
Name() string
|
|
|
|
// Send delivers msg to peer. Implementations may zero-copy directly
|
|
// from GPU memory; the caller MUST NOT reuse the msg buffer until
|
|
// the call returns.
|
|
Send(ctx context.Context, peer string, msg []byte) error
|
|
|
|
// Recv blocks until the next message arrives or ctx is cancelled.
|
|
// Returned msg may alias a GPU-mapped buffer; the caller MUST copy
|
|
// before returning the buffer to the transport.
|
|
Recv(ctx context.Context) (peer string, msg []byte, err error)
|
|
|
|
// Close releases any held resources (sockets, GPU mapped regions,
|
|
// DPDK queues, IB QPs).
|
|
Close() error
|
|
|
|
// Caps reports what this transport supports. Callers that need
|
|
// GPU-resident bytes (UMA / GPUDirect) gate on Caps().GPUResident
|
|
// at construction time, not per-call.
|
|
Caps() Capabilities
|
|
}
|
|
|
|
// Buffer is one slab of managed memory handed out by a BufferAllocator.
|
|
// For the UMA transport on Linux+CUDA, the underlying pointer is
|
|
// cudaMallocManaged memory — the same bytes the GPU sees. Calling Bytes()
|
|
// gives a Go slice that shares the storage; calling DevicePtr() gives the
|
|
// raw device pointer for CUDA kernel launches.
|
|
//
|
|
// Release returns the buffer to its allocator. Double-Release is a no-op
|
|
// (allocators detect and ignore it).
|
|
type Buffer interface {
|
|
// Bytes returns a Go slice aliasing the buffer. Length is the
|
|
// requested size, not the slab class size. Mutating the slice
|
|
// mutates GPU-visible memory (when GPU-resident).
|
|
Bytes() []byte
|
|
|
|
// DevicePtr returns the raw device pointer. For UMA this is the
|
|
// same address as &Bytes()[0] (unified memory). For non-UMA
|
|
// allocators this is nil and the caller must not pass it to CUDA.
|
|
DevicePtr() unsafe.Pointer
|
|
|
|
// Release returns the buffer to its allocator. The slice returned
|
|
// by Bytes() must not be used after Release.
|
|
Release()
|
|
}
|
|
|
|
// BufferAllocator hands out managed buffers. Transports that can deliver
|
|
// GPU-resident bytes (UMA Linux+CUDA, UMA Darwin+Metal) implement this;
|
|
// transports that can't (default TCP) return ErrNotAvailable from
|
|
// AllocBuffer so the caller knows to fall back to heap allocation.
|
|
//
|
|
// Capacity in bytes is the operator-configurable pool size (default 4
|
|
// GiB); allocations from a full pool block until a buffer is Released.
|
|
type BufferAllocator interface {
|
|
// AllocBuffer returns a buffer of at least `size` bytes. The
|
|
// returned Buffer.Bytes() slice has length `size` even though the
|
|
// underlying slab class may be larger.
|
|
AllocBuffer(size int) (Buffer, error)
|
|
|
|
// PoolBytes returns the total bytes currently owned by the pool
|
|
// (configurable maximum). Used for metrics.
|
|
PoolBytes() int
|
|
}
|
|
|
|
// Capabilities describes what a Transport implementation supports.
|
|
type Capabilities struct {
|
|
// GPUResident is true when message bytes never visit CPU memory.
|
|
GPUResident bool
|
|
// ZeroCopy is true when no memcpy happens between NIC and the buffer
|
|
// returned by Recv. (GPU residency implies zero copy; the reverse
|
|
// does not — UMA is GPU-resident-and-zero-copy without DMA.)
|
|
ZeroCopy bool
|
|
// MinLatencyMicros is a hint for the implementation's floor latency
|
|
// on a warm path (informational; not a contract).
|
|
MinLatencyMicros float64
|
|
}
|
|
|
|
// ErrNotAvailable is returned by Pick when no transport at the requested
|
|
// preference level is buildable on this host.
|
|
var ErrNotAvailable = errors.New("zap/transport: requested transport not available on this host")
|
|
|
|
// envPreference is the env var operators set to force a transport. Empty
|
|
// means "use the best available". Useful for A/B benchmarking on the
|
|
// same hardware (e.g. forcing default over UMA to measure the UMA win).
|
|
const envPreference = "ZAP_TRANSPORT"
|
|
|
|
// Pick returns the requested transport, falling back to the next-best
|
|
// available implementation when the request is unavailable. Empty
|
|
// preference selects automatically: gpudirect > dpdk > uma > default.
|
|
//
|
|
// The preference string is matched case-insensitively against Name().
|
|
// Special value "default" returns the standard transport unconditionally.
|
|
//
|
|
// Pick reads ZAP_TRANSPORT once; subsequent changes to the env var have
|
|
// no effect on already-constructed transports.
|
|
//
|
|
// First call also emits one log line listing the transports that were
|
|
// probed and which won. Set ZAP_TRANSPORT_QUIET=1 to silence.
|
|
func Pick(preferred string) (Transport, error) {
|
|
if preferred == "" {
|
|
preferred = os.Getenv(envPreference)
|
|
}
|
|
preferred = strings.ToLower(strings.TrimSpace(preferred))
|
|
|
|
// Explicit request — try only that transport, no fallback.
|
|
if preferred != "" && preferred != "auto" {
|
|
t, err := newByName(preferred)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("%w: name=%q: %v", ErrNotAvailable, preferred, err)
|
|
}
|
|
logPickOnce(t.Name(), preferred, nil)
|
|
return t, nil
|
|
}
|
|
|
|
// Auto: try each native path in preference order; fall back to default.
|
|
probed := map[string]error{}
|
|
for _, name := range []string{"gpudirect", "dpdk", "uma"} {
|
|
t, err := newByName(name)
|
|
if err == nil {
|
|
logPickOnce(t.Name(), "auto", probed)
|
|
return t, nil
|
|
}
|
|
probed[name] = err
|
|
}
|
|
t := newDefault()
|
|
logPickOnce(t.Name(), "auto-fallback", probed)
|
|
return t, nil
|
|
}
|
|
|
|
var (
|
|
logOnce sync.Once
|
|
)
|
|
|
|
// logPickOnce emits one structured log line the first time Pick succeeds.
|
|
// Subsequent calls are silent — the log line is for boot-time visibility,
|
|
// not steady-state observability.
|
|
func logPickOnce(name, mode string, probed map[string]error) {
|
|
if os.Getenv("ZAP_TRANSPORT_QUIET") == "1" {
|
|
return
|
|
}
|
|
logOnce.Do(func() {
|
|
attrs := []any{
|
|
slog.String("transport", name),
|
|
slog.String("mode", mode),
|
|
}
|
|
for n, err := range probed {
|
|
attrs = append(attrs, slog.String("probe."+n, err.Error()))
|
|
}
|
|
slog.Info("zap/transport: selected", attrs...)
|
|
})
|
|
}
|
|
|
|
// Registered returns the list of transports that this binary can attempt
|
|
// to instantiate (i.e. the platform-and-build-tag matrix). Used by tools
|
|
// like `zap-info` and the boot-time diagnostic in lqd.
|
|
func Registered() []string {
|
|
return []string{"default", "uma", "gpudirect", "dpdk"}
|
|
}
|
|
|
|
// newByName routes to the per-impl factory. The actual implementations
|
|
// live in transport_<name>.go files behind appropriate build tags; the
|
|
// unavailable factories on this build return ErrNotAvailable.
|
|
func newByName(name string) (Transport, error) {
|
|
switch name {
|
|
case "default":
|
|
return newDefault(), nil
|
|
case "uma":
|
|
return newUMA()
|
|
case "gpudirect":
|
|
return newGPUDirect()
|
|
case "dpdk":
|
|
return newDPDK()
|
|
default:
|
|
return nil, fmt.Errorf("unknown transport name %q", name)
|
|
}
|
|
}
|