| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| #include <errno.h> |
| #include <fcntl.h> |
| #include <limits.h> |
| #include <netdb.h> |
| #include <stdarg.h> |
| #include <sys/uio.h> |
| #include <netinet/in.h> |
| #include <netinet/tcp.h> |
| #include <pthread.h> |
| #include <stdatomic.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <sys/socket.h> |
| #include <sys/stat.h> |
| #include <time.h> |
| #include <unistd.h> |
|
|
| #include "ds4_tp.h" |
|
|
| #if defined(__APPLE__) && defined(__has_include) |
| #if __has_include(<infiniband/verbs.h>) |
| #include <infiniband/verbs.h> |
| #include <dlfcn.h> |
| #define DS4_TP_HAVE_VERBS 1 |
| #endif |
| #endif |
|
|
| #define DS4_TP_MAGIC UINT32_C(0x44533454) |
| #define DS4_TP_BATCH_MAGIC UINT32_C(0x44533442) |
| #define DS4_TP_PROTOCOL_VERSION 7u |
|
|
| |
| |
| #define DS4_TP_DEFAULT_TIMEOUT_SEC 300 |
|
|
| typedef struct { |
| uint32_t magic; |
| uint32_t type; |
| uint32_t bytes; |
| } ds4_tp_frame_header; |
|
|
| typedef struct { |
| uint32_t magic; |
| uint32_t version; |
| uint32_t role; |
| uint32_t rdma_ok; |
| uint64_t gguf_bytes; |
| uint32_t model_id; |
| uint32_t n_layer; |
| uint32_t n_embd; |
| uint32_t n_vocab; |
| uint32_t quant_bits; |
| uint32_t ctx_size; |
| uint32_t gate_slot_start; |
| uint32_t gate_slot_step; |
| uint32_t gates_per_token; |
| uint32_t pad; |
| } ds4_tp_hello_fixed; |
|
|
| typedef struct { |
| uint64_t slab_base; |
| uint32_t rkey; |
| uint32_t qpn; |
| uint32_t psn; |
| uint32_t mtu; |
| uint16_t lid; |
| uint8_t gid[16]; |
| uint8_t link_layer; |
| } ds4_tp_rdma_info; |
|
|
| |
| |
| typedef struct { |
| uint32_t magic; |
| uint16_t layer; |
| uint16_t gate; |
| uint64_t seq; |
| } ds4_tp_gate_header; |
|
|
| #ifdef DS4_TP_HAVE_VERBS |
| |
| |
| |
| |
| typedef struct { |
| void *handle; |
| struct ibv_device **(*get_device_list)(int *); |
| void (*free_device_list)(struct ibv_device **); |
| const char *(*get_device_name)(struct ibv_device *); |
| struct ibv_context *(*open_device)(struct ibv_device *); |
| int (*close_device)(struct ibv_context *); |
| int (*query_device)(struct ibv_context *, struct ibv_device_attr *); |
| int (*query_port)(struct ibv_context *, uint8_t, struct ibv_port_attr *); |
| int (*query_gid)(struct ibv_context *, uint8_t, int, union ibv_gid *); |
| struct ibv_pd *(*alloc_pd)(struct ibv_context *); |
| int (*dealloc_pd)(struct ibv_pd *); |
| struct ibv_mr *(*reg_mr)(struct ibv_pd *, void *, size_t, int); |
| int (*dereg_mr)(struct ibv_mr *); |
| struct ibv_cq *(*create_cq)(struct ibv_context *, int, void *, struct ibv_comp_channel *, int); |
| int (*destroy_cq)(struct ibv_cq *); |
| struct ibv_qp *(*create_qp)(struct ibv_pd *, struct ibv_qp_init_attr *); |
| int (*destroy_qp)(struct ibv_qp *); |
| int (*modify_qp)(struct ibv_qp *, struct ibv_qp_attr *, int); |
| } ds4_tp_verbs_api; |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| #define DS4_TP_RDMA_MAX_MSG 16384 |
| #define DS4_TP_RDMA_RECV_WINDOW 16 |
| #define DS4_TP_RDMA_BULK_SLOTS 64 |
| #define DS4_TP_RDMA_BULK_WR_TAG (UINT64_C(1) << 63) |
|
|
| typedef struct { |
| ds4_tp_verbs_api api; |
| struct ibv_context *ctx; |
| struct ibv_pd *pd; |
| struct ibv_cq *cq; |
| struct ibv_qp *qp; |
| struct ibv_mr *mr; |
| struct ibv_port_attr port; |
| union ibv_gid gid; |
| int gid_index; |
| uint32_t max_inline; |
| ds4_tp_rdma_info peer; |
| uint32_t send_outstanding; |
| uint64_t recv_done; |
| uint64_t last_gate_seq; |
| bool recv_window_active; |
| pthread_mutex_t post_lock; |
| } ds4_tp_rdma; |
| #endif |
|
|
| struct ds4_tp { |
| ds4_tp_options opt; |
| int rank; |
| int control_fd; |
| int data_fd; |
| bool rdma_active; |
| uint32_t peer_ctx; |
| uint32_t n_layer; |
| uint32_t n_embd; |
| uint64_t vec_bytes; |
| uint32_t n_slots; |
| |
| uint32_t gate_slot_start; |
| uint32_t gate_slot_step; |
| uint32_t gates_per_token; |
| uint8_t *slab; |
| uint64_t slab_bytes; |
| |
| uint64_t out_off; |
| uint64_t in_off; |
| uint64_t in_flags_off; |
| uint64_t token_off; |
| uint64_t out_flags_off; |
| uint64_t gpu_flags_off; |
| uint64_t batch_out_off; |
| uint64_t batch_in_off; |
| uint64_t timeout_sec; |
| atomic_bool failed; |
| #ifdef DS4_TP_HAVE_VERBS |
| ds4_tp_rdma rdma; |
| #endif |
| }; |
|
|
| |
| |
| |
|
|
| static double tp_now_sec(void) { |
| struct timespec ts; |
| clock_gettime(CLOCK_MONOTONIC, &ts); |
| return (double)ts.tv_sec + (double)ts.tv_nsec / 1e9; |
| } |
|
|
| static void tp_set_err(char *err, size_t errlen, const char *fmt, ...) { |
| if (!err || !errlen) return; |
| va_list ap; |
| va_start(ap, fmt); |
| vsnprintf(err, errlen, fmt, ap); |
| va_end(ap); |
| } |
|
|
| static int tp_write_full(int fd, const void *buf, size_t len) { |
| const char *p = buf; |
| while (len) { |
| #ifdef MSG_NOSIGNAL |
| ssize_t w = send(fd, p, len, MSG_NOSIGNAL); |
| #else |
| ssize_t w = send(fd, p, len, 0); |
| #endif |
| if (w < 0) { |
| if (errno == EINTR) continue; |
| return 0; |
| } |
| if (w == 0) return 0; |
| p += w; |
| len -= (size_t)w; |
| } |
| return 1; |
| } |
|
|
| static int tp_read_full(int fd, void *buf, size_t len) { |
| char *p = buf; |
| while (len) { |
| ssize_t r = read(fd, p, len); |
| if (r < 0) { |
| if (errno == EINTR) continue; |
| return 0; |
| } |
| if (r == 0) return 0; |
| p += r; |
| len -= (size_t)r; |
| } |
| return 1; |
| } |
|
|
| static void tp_socket_tune(int fd) { |
| int one = 1; |
| #ifdef SO_NOSIGPIPE |
| setsockopt(fd, SOL_SOCKET, SO_NOSIGPIPE, &one, sizeof(one)); |
| #endif |
| setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &one, sizeof(one)); |
| |
| |
| int sz = 4 * 1024 * 1024; |
| setsockopt(fd, SOL_SOCKET, SO_SNDBUF, &sz, sizeof(sz)); |
| setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &sz, sizeof(sz)); |
| } |
|
|
| #ifdef DS4_TP_HAVE_VERBS |
| |
| |
| |
| static int tp_peer_closed(const ds4_tp *tp) { |
| char byte; |
| const ssize_t n = recv(tp->control_fd, &byte, 1, |
| MSG_PEEK | MSG_DONTWAIT); |
| if (n == 0) return 1; |
| if (n > 0) return 0; |
| return errno != EAGAIN && errno != EWOULDBLOCK && errno != EINTR; |
| } |
| #endif |
|
|
| static int tp_listen(const char *host, int port, char *err, size_t errlen) { |
| char portbuf[16]; |
| snprintf(portbuf, sizeof(portbuf), "%d", port); |
| struct addrinfo hints = {0}, *res = NULL; |
| hints.ai_family = AF_UNSPEC; |
| hints.ai_socktype = SOCK_STREAM; |
| hints.ai_flags = AI_PASSIVE; |
| int rc = getaddrinfo(host && host[0] ? host : NULL, portbuf, &hints, &res); |
| if (rc != 0) { |
| tp_set_err(err, errlen, "tp listen resolve %s:%d: %s", host, port, gai_strerror(rc)); |
| return -1; |
| } |
| int fd = -1; |
| for (struct addrinfo *ai = res; ai; ai = ai->ai_next) { |
| fd = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol); |
| if (fd < 0) continue; |
| int one = 1; |
| setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)); |
| if (bind(fd, ai->ai_addr, ai->ai_addrlen) == 0 && listen(fd, 2) == 0) break; |
| close(fd); |
| fd = -1; |
| } |
| freeaddrinfo(res); |
| if (fd < 0) tp_set_err(err, errlen, "tp listen %s:%d: %s", host, port, strerror(errno)); |
| return fd; |
| } |
|
|
| static int tp_dial(const char *host, int port, double timeout_sec, char *err, size_t errlen) { |
| char portbuf[16]; |
| snprintf(portbuf, sizeof(portbuf), "%d", port); |
| double deadline = tp_now_sec() + timeout_sec; |
| int last_errno = 0; |
| uint32_t attempts = 0; |
| do { |
| struct addrinfo hints = {0}, *res = NULL; |
| hints.ai_family = AF_UNSPEC; |
| hints.ai_socktype = SOCK_STREAM; |
| int gai = getaddrinfo(host, portbuf, &hints, &res); |
| if (gai == 0) { |
| for (struct addrinfo *ai = res; ai; ai = ai->ai_next) { |
| int fd = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol); |
| if (fd < 0) continue; |
| if (connect(fd, ai->ai_addr, ai->ai_addrlen) == 0) { |
| freeaddrinfo(res); |
| return fd; |
| } |
| last_errno = errno; |
| close(fd); |
| } |
| freeaddrinfo(res); |
| } |
| |
| |
| if (attempts++ % 50 == 0) { |
| fprintf(stderr, "ds4-tp: connecting to %s:%d ... (%s)\n", host, port, |
| gai != 0 ? gai_strerror(gai) : |
| last_errno ? strerror(last_errno) : "no address worked"); |
| } |
| usleep(200 * 1000); |
| } while (tp_now_sec() < deadline); |
| tp_set_err(err, errlen, "tp connect %s:%d: %s", host, port, |
| last_errno ? strerror(last_errno) : "unreachable"); |
| return -1; |
| } |
|
|
| static int tp_send_frame(int fd, uint32_t type, const void *payload, uint32_t bytes) { |
| ds4_tp_frame_header h = { DS4_TP_MAGIC, type, bytes }; |
| if (!tp_write_full(fd, &h, sizeof(h))) return 0; |
| if (bytes && !tp_write_full(fd, payload, bytes)) return 0; |
| return 1; |
| } |
|
|
| static int tp_read_frame_header(int fd, uint32_t *type, uint32_t *bytes) { |
| ds4_tp_frame_header h; |
| if (!tp_read_full(fd, &h, sizeof(h))) return 0; |
| if (h.magic != DS4_TP_MAGIC) return 0; |
| *type = h.type; |
| *bytes = h.bytes; |
| return 1; |
| } |
|
|
| |
| |
| |
|
|
| bool ds4_tp_enabled(const ds4_tp_options *opt) { |
| return opt && opt->role != DS4_TP_NONE; |
| } |
|
|
| void ds4_tp_usage(FILE *fp) { |
| fprintf(fp, |
| "Tensor parallelism (two identical machines):\n" |
| " --tensor-parallel Use --role/--listen/--coordinator for a 50/50 TP pair.\n" |
| " --transport <auto|rdma|tcp> Gate transport (default auto).\n" |
| " --rdma-device <name> Select a verbs device such as rdma_en1.\n" |
| " --rdma-gid-index <n> Select the local verbs GID index.\n" |
| " --tensor-parallel-token-prefill\n" |
| " GLM diagnostic: prefill one token at a time.\n" |
| " --debug-hash <n> Cross-check hidden state every n tokens.\n"); |
| } |
|
|
| int ds4_tp_parse_cli_arg( |
| const char *arg, |
| int *index, |
| int argc, |
| char **argv, |
| ds4_tp_options *opt, |
| char *err, |
| size_t errlen) |
| { |
| int i = *index; |
| if (!strcmp(arg, "--tensor-parallel")) { |
| opt->requested = true; |
| } else if (!strcmp(arg, "--transport")) { |
| if (i + 1 >= argc) goto missing; |
| const char *v = argv[++i]; |
| if (!strcmp(v, "auto")) opt->transport = DS4_TP_TRANSPORT_AUTO; |
| else if (!strcmp(v, "rdma")) opt->transport = DS4_TP_TRANSPORT_RDMA; |
| else if (!strcmp(v, "tcp")) opt->transport = DS4_TP_TRANSPORT_TCP; |
| else { |
| tp_set_err(err, errlen, "invalid %s value: %s", arg, v); |
| return DS4_TP_CLI_ERROR; |
| } |
| } else if (!strcmp(arg, "--rdma-device")) { |
| if (i + 1 >= argc) goto missing; |
| opt->rdma_device = argv[++i]; |
| } else if (!strcmp(arg, "--rdma-gid-index")) { |
| if (i + 1 >= argc) goto missing; |
| char *end = NULL; |
| errno = 0; |
| long value = strtol(argv[++i], &end, 10); |
| if (errno != 0 || !end || *end != '\0' || value < 0 || value > INT_MAX) { |
| tp_set_err(err, errlen, "invalid --rdma-gid-index %s", argv[i]); |
| return DS4_TP_CLI_ERROR; |
| } |
| opt->rdma_gid_index = (int)value; |
| opt->rdma_gid_index_set = true; |
| } else if (!strcmp(arg, "--tensor-parallel-token-prefill")) { |
| opt->glm_token_prefill = true; |
| } else if (!strcmp(arg, "--debug-hash")) { |
| if (i + 1 >= argc) goto missing; |
| opt->debug_hash = atoi(argv[++i]); |
| } else { |
| return DS4_TP_CLI_NOT_MATCHED; |
| } |
| *index = i; |
| return DS4_TP_CLI_MATCHED; |
| missing: |
| tp_set_err(err, errlen, "%s requires an argument", arg); |
| return DS4_TP_CLI_ERROR; |
| } |
|
|
| int ds4_tp_adopt_distributed_options( |
| ds4_tp_options *tp, |
| ds4_distributed_options *dist, |
| char *err, |
| size_t errlen) |
| { |
| if (!tp || !dist || !tp->requested) return 1; |
| if (tp->role != DS4_TP_NONE) { |
| tp_set_err(err, errlen, |
| "--tensor-parallel selects its role through --role"); |
| return 0; |
| } |
| if (dist->role == DS4_DISTRIBUTED_NONE) { |
| tp_set_err(err, errlen, |
| "--tensor-parallel requires --role coordinator or --role worker"); |
| return 0; |
| } |
| if (dist->layers.set) { |
| tp_set_err(err, errlen, |
| "tensor parallelism always uses one 50/50 worker; omit --layers"); |
| return 0; |
| } |
| if (dist->prefill_chunk || dist->prefill_window || dist->activation_bits || |
| dist->replay_check || dist->debug) { |
| tp_set_err(err, errlen, |
| "--dist-* and distributed debug options cannot be used with --tensor-parallel"); |
| return 0; |
| } |
|
|
| if (dist->role == DS4_DISTRIBUTED_COORDINATOR) { |
| if (!dist->listen_host || dist->listen_port <= 0) { |
| tp_set_err(err, errlen, |
| "--role coordinator --tensor-parallel requires --listen HOST PORT"); |
| return 0; |
| } |
| if (dist->coordinator_host || dist->coordinator_port) { |
| tp_set_err(err, errlen, |
| "--role coordinator must not use --coordinator"); |
| return 0; |
| } |
| tp->role = DS4_TP_LEADER; |
| tp->listen_host = dist->listen_host; |
| tp->listen_port = dist->listen_port; |
| } else if (dist->role == DS4_DISTRIBUTED_WORKER) { |
| if (!dist->coordinator_host || dist->coordinator_port <= 0) { |
| tp_set_err(err, errlen, |
| "--role worker --tensor-parallel requires --coordinator HOST PORT"); |
| return 0; |
| } |
| if (dist->listen_host || dist->listen_port) { |
| tp_set_err(err, errlen, |
| "--role worker --tensor-parallel must not use --listen"); |
| return 0; |
| } |
| tp->role = DS4_TP_WORKER; |
| tp->leader_host = dist->coordinator_host; |
| tp->leader_port = dist->coordinator_port; |
| } else { |
| tp_set_err(err, errlen, "invalid tensor-parallel role"); |
| return 0; |
| } |
|
|
| memset(dist, 0, sizeof(*dist)); |
| return 1; |
| } |
|
|
| int ds4_tp_validate_engine_options( |
| const ds4_engine_options *opt, |
| char *err, |
| size_t errlen) |
| { |
| if (!ds4_tp_enabled(&opt->tp)) { |
| if (opt->tp.requested || opt->tp.transport != DS4_TP_TRANSPORT_AUTO || |
| opt->tp.rdma_device || opt->tp.rdma_gid_index_set || |
| opt->tp.glm_token_prefill || opt->tp.debug_hash != 0) { |
| tp_set_err(err, errlen, |
| "tensor-parallel options require --tensor-parallel and --role"); |
| return 0; |
| } |
| return 1; |
| } |
| if (opt->backend != DS4_BACKEND_METAL) { |
| tp_set_err(err, errlen, "tensor parallelism requires the Metal backend"); |
| return 0; |
| } |
| if (opt->ssd_streaming) { |
| tp_set_err(err, errlen, "tensor parallelism requires resident weights (no --ssd-streaming)"); |
| return 0; |
| } |
| if (opt->distributed.role != DS4_DISTRIBUTED_NONE) { |
| tp_set_err(err, errlen, "tensor parallelism and --role distributed modes are exclusive"); |
| return 0; |
| } |
| |
| |
| |
| if (opt->load_slice) { |
| tp_set_err(err, errlen, "tensor parallelism does not use distributed layer slices"); |
| return 0; |
| } |
| return 1; |
| } |
|
|
| |
| |
| |
|
|
| uint64_t ds4_tp_slab_bytes(uint32_t n_layer, uint32_t n_embd) { |
| uint64_t vec = (uint64_t)n_embd * sizeof(float); |
| uint64_t slots = (uint64_t)n_layer * DS4_TP_GATES_PER_LAYER; |
| return slots * vec * 2 + |
| slots * 8 * 2 + |
| 16 + |
| slots * 4 + |
| (uint64_t)n_layer * DS4_TP_BATCH_MAX_ROWS * vec * 2; |
| } |
|
|
| static void tp_slab_layout(ds4_tp *tp) { |
| uint64_t vec = tp->vec_bytes; |
| uint64_t slots = tp->n_slots; |
| tp->out_off = 0; |
| tp->in_off = slots * vec; |
| tp->in_flags_off = tp->in_off + slots * vec; |
| tp->token_off = tp->in_flags_off + slots * 8; |
| tp->out_flags_off = tp->token_off + 16; |
| tp->gpu_flags_off = tp->out_flags_off + slots * 8; |
| tp->batch_out_off = tp->gpu_flags_off + slots * 4; |
| tp->batch_in_off = tp->batch_out_off + |
| (uint64_t)tp->n_layer * DS4_TP_BATCH_MAX_ROWS * vec; |
| tp->slab_bytes = tp->batch_in_off + |
| (uint64_t)tp->n_layer * DS4_TP_BATCH_MAX_ROWS * vec; |
| } |
|
|
| uint64_t ds4_tp_slab_gpu_flags_offset(const ds4_tp *tp) { |
| return tp->gpu_flags_off; |
| } |
|
|
| static uint32_t tp_slot(const ds4_tp *tp, uint32_t layer, uint32_t gate) { |
| (void)tp; |
| return layer * DS4_TP_GATES_PER_LAYER + gate; |
| } |
|
|
| uint64_t ds4_tp_slab_out_offset(const ds4_tp *tp, uint32_t layer, uint32_t gate) { |
| return tp->out_off + (uint64_t)tp_slot(tp, layer, gate) * tp->vec_bytes; |
| } |
|
|
| uint64_t ds4_tp_slab_in_offset(const ds4_tp *tp, uint32_t layer, uint32_t gate) { |
| return tp->in_off + (uint64_t)tp_slot(tp, layer, gate) * tp->vec_bytes; |
| } |
|
|
| uint64_t ds4_tp_slab_batch_out_offset(const ds4_tp *tp, uint32_t layer) { |
| return tp->batch_out_off + |
| (uint64_t)layer * DS4_TP_BATCH_MAX_ROWS * tp->vec_bytes; |
| } |
|
|
| uint64_t ds4_tp_slab_batch_in_offset(const ds4_tp *tp, uint32_t layer) { |
| return tp->batch_in_off + |
| (uint64_t)layer * DS4_TP_BATCH_MAX_ROWS * tp->vec_bytes; |
| } |
|
|
| |
| |
| |
|
|
| #ifdef DS4_TP_HAVE_VERBS |
|
|
| static int tp_rdma_load_api(ds4_tp_verbs_api *api) { |
| if (api->handle) return 1; |
| void *h = dlopen("/usr/lib/librdma.dylib", RTLD_NOW | RTLD_LOCAL); |
| if (!h) h = dlopen("librdma.dylib", RTLD_NOW | RTLD_LOCAL); |
| if (!h) return 0; |
| #define TP_SYM(field, name) \ |
| do { \ |
| api->field = (__typeof__(api->field))dlsym(h, name); \ |
| if (!api->field) { dlclose(h); return 0; } \ |
| } while (0) |
| TP_SYM(get_device_list, "ibv_get_device_list"); |
| TP_SYM(free_device_list, "ibv_free_device_list"); |
| TP_SYM(get_device_name, "ibv_get_device_name"); |
| TP_SYM(open_device, "ibv_open_device"); |
| TP_SYM(close_device, "ibv_close_device"); |
| TP_SYM(query_device, "ibv_query_device"); |
| TP_SYM(query_port, "ibv_query_port"); |
| TP_SYM(query_gid, "ibv_query_gid"); |
| TP_SYM(alloc_pd, "ibv_alloc_pd"); |
| TP_SYM(dealloc_pd, "ibv_dealloc_pd"); |
| TP_SYM(reg_mr, "ibv_reg_mr"); |
| TP_SYM(dereg_mr, "ibv_dereg_mr"); |
| TP_SYM(create_cq, "ibv_create_cq"); |
| TP_SYM(destroy_cq, "ibv_destroy_cq"); |
| TP_SYM(create_qp, "ibv_create_qp"); |
| TP_SYM(destroy_qp, "ibv_destroy_qp"); |
| TP_SYM(modify_qp, "ibv_modify_qp"); |
| #undef TP_SYM |
| api->handle = h; |
| return 1; |
| } |
|
|
| |
| static int tp_rdma_probe(ds4_tp_verbs_api *api) { |
| if (!tp_rdma_load_api(api)) return 0; |
| int num = 0; |
| struct ibv_device **devs = api->get_device_list(&num); |
| if (!devs) return 0; |
| api->free_device_list(devs); |
| return num > 0; |
| } |
|
|
| static int tp_rdma_open(ds4_tp *tp, char *err, size_t errlen) { |
| ds4_tp_rdma *r = &tp->rdma; |
| int num = 0; |
| struct ibv_device **devs = r->api.get_device_list(&num); |
| if (!devs || num == 0) { |
| tp_set_err(err, errlen, "tp rdma: no verbs devices"); |
| if (devs) r->api.free_device_list(devs); |
| return 0; |
| } |
| |
| |
| const char *want_name = tp->opt.rdma_device; |
| char states[256] = ""; |
| for (int i = 0; i < num && !r->ctx; i++) { |
| const char *name = r->api.get_device_name(devs[i]); |
| if (want_name && strcmp(want_name, name) != 0) continue; |
| struct ibv_context *ctx = r->api.open_device(devs[i]); |
| if (!ctx) continue; |
| struct ibv_port_attr pa; |
| if (r->api.query_port(ctx, 1, &pa) == 0 && |
| (pa.state == IBV_PORT_ACTIVE || want_name)) { |
| r->ctx = ctx; |
| r->port = pa; |
| fprintf(stderr, "ds4-tp: rdma device %s (port state %d)\n", name, (int)pa.state); |
| break; |
| } |
| size_t off = strlen(states); |
| snprintf(states + off, sizeof(states) - off, "%s%s=%d", |
| off ? ", " : "", name, (int)pa.state); |
| r->api.close_device(ctx); |
| } |
| r->api.free_device_list(devs); |
| if (!r->ctx) { |
| tp_set_err(err, errlen, |
| "tp rdma: no device with an active port (%s); is the peer up " |
| "and rdma_ctl enabled on both machines?", states); |
| return 0; |
| } |
| |
| |
| |
| |
| r->gid_index = -1; |
| if (tp->opt.rdma_gid_index_set) { |
| r->gid_index = tp->opt.rdma_gid_index; |
| if (r->api.query_gid(r->ctx, 1, r->gid_index, &r->gid) != 0) { |
| tp_set_err(err, errlen, "tp rdma: query_gid(%d): %s", |
| r->gid_index, strerror(errno)); |
| return 0; |
| } |
| } else { |
| for (int i = 0; i < r->port.gid_tbl_len; i++) { |
| union ibv_gid tmp; |
| if (r->api.query_gid(r->ctx, 1, i, &tmp) != 0) continue; |
| uint64_t hi; |
| uint16_t mid, v4tag; |
| memcpy(&hi, &tmp.raw[0], 8); |
| memcpy(&mid, &tmp.raw[8], 2); |
| memcpy(&v4tag, &tmp.raw[10], 2); |
| if (hi == 0 && mid == 0 && v4tag == 0xffff) { |
| r->gid = tmp; |
| r->gid_index = i; |
| break; |
| } |
| } |
| if (r->gid_index < 0) { |
| tp_set_err(err, errlen, |
| "tp rdma: no IPv4-mapped GID on the active port; give the " |
| "Thunderbolt interface its own IPv4 (e.g. sudo ifconfig en1 " |
| "inet 10.99.0.2/30 alias) on both machines"); |
| return 0; |
| } |
| } |
| r->pd = r->api.alloc_pd(r->ctx); |
| if (!r->pd) { |
| tp_set_err(err, errlen, "tp rdma: alloc_pd failed"); |
| return 0; |
| } |
| r->cq = r->api.create_cq(r->ctx, 512, NULL, NULL, 0); |
| if (!r->cq) { |
| tp_set_err(err, errlen, "tp rdma: create_cq failed"); |
| return 0; |
| } |
| struct ibv_qp_init_attr qia = {0}; |
| qia.send_cq = r->cq; |
| qia.recv_cq = r->cq; |
| qia.qp_type = IBV_QPT_UC; |
| qia.cap.max_send_wr = 256; |
| qia.cap.max_recv_wr = 64; |
| qia.cap.max_send_sge = 1; |
| qia.cap.max_recv_sge = 1; |
| qia.cap.max_inline_data = 0; |
| r->qp = r->api.create_qp(r->pd, &qia); |
| if (!r->qp) { |
| tp_set_err(err, errlen, "tp rdma: create_qp(UC): %s", strerror(errno)); |
| return 0; |
| } |
| r->max_inline = qia.cap.max_inline_data; |
|
|
| pthread_mutex_init(&r->post_lock, NULL); |
| return 1; |
| } |
|
|
| static int tp_rdma_post_gate_recv(ds4_tp *tp, uint64_t seq); |
|
|
| static int tp_rdma_register_and_exchange(ds4_tp *tp, char *err, size_t errlen) { |
| ds4_tp_rdma *r = &tp->rdma; |
| r->mr = r->api.reg_mr(r->pd, tp->slab, tp->slab_bytes, |
| IBV_ACCESS_LOCAL_WRITE | IBV_ACCESS_REMOTE_READ | |
| IBV_ACCESS_REMOTE_WRITE); |
| if (!r->mr) { |
| tp_set_err(err, errlen, "tp rdma: reg_mr(%llu bytes): %s", |
| (unsigned long long)tp->slab_bytes, strerror(errno)); |
| return 0; |
| } |
| ds4_tp_rdma_info mine = {0}; |
| mine.slab_base = (uint64_t)(uintptr_t)tp->slab; |
| mine.rkey = r->mr->rkey; |
| mine.qpn = r->qp->qp_num; |
| mine.psn = (uint32_t)(getpid() ^ (uintptr_t)tp) & 0xffffff; |
| mine.mtu = (uint32_t)r->port.active_mtu; |
| mine.lid = r->port.lid; |
| memcpy(mine.gid, r->gid.raw, 16); |
| mine.link_layer = r->port.link_layer; |
| if (!tp_send_frame(tp->control_fd, DS4_TP_FRAME_RDMA_INFO, &mine, sizeof(mine))) { |
| tp_set_err(err, errlen, "tp rdma: info send failed"); |
| return 0; |
| } |
| uint32_t type = 0, bytes = 0; |
| if (!tp_read_frame_header(tp->control_fd, &type, &bytes) || |
| type != DS4_TP_FRAME_RDMA_INFO || bytes != sizeof(r->peer) || |
| !tp_read_full(tp->control_fd, &r->peer, sizeof(r->peer))) { |
| tp_set_err(err, errlen, "tp rdma: info recv failed"); |
| return 0; |
| } |
|
|
| |
| |
| struct ibv_qp_attr a = {0}; |
| a.qp_state = IBV_QPS_INIT; |
| a.pkey_index = 0; |
| a.port_num = 1; |
| a.qp_access_flags = IBV_ACCESS_LOCAL_WRITE | IBV_ACCESS_REMOTE_READ | |
| IBV_ACCESS_REMOTE_WRITE; |
| if (r->api.modify_qp(r->qp, &a, |
| IBV_QP_STATE | IBV_QP_PKEY_INDEX | IBV_QP_PORT | IBV_QP_ACCESS_FLAGS) != 0) { |
| tp_set_err(err, errlen, "tp rdma: modify INIT: %s", strerror(errno)); |
| return 0; |
| } |
| memset(&a, 0, sizeof(a)); |
| a.qp_state = IBV_QPS_RTR; |
| a.path_mtu = IBV_MTU_1024; |
| a.dest_qp_num = r->peer.qpn; |
| a.rq_psn = r->peer.psn; |
| a.ah_attr.dlid = (uint16_t)r->peer.lid; |
| a.ah_attr.port_num = 1; |
| a.ah_attr.is_global = 1; |
| memcpy(a.ah_attr.grh.dgid.raw, r->peer.gid, 16); |
| a.ah_attr.grh.sgid_index = (uint8_t)r->gid_index; |
| a.ah_attr.grh.hop_limit = 1; |
| if (r->api.modify_qp(r->qp, &a, |
| IBV_QP_STATE | IBV_QP_AV | IBV_QP_PATH_MTU | IBV_QP_DEST_QPN | |
| IBV_QP_RQ_PSN) != 0) { |
| tp_set_err(err, errlen, "tp rdma: modify RTR: %s", strerror(errno)); |
| return 0; |
| } |
| memset(&a, 0, sizeof(a)); |
| a.qp_state = IBV_QPS_RTS; |
| a.sq_psn = mine.psn; |
| if (r->api.modify_qp(r->qp, &a, IBV_QP_STATE | IBV_QP_SQ_PSN) != 0) { |
| tp_set_err(err, errlen, "tp rdma: modify RTS: %s", strerror(errno)); |
| return 0; |
| } |
| if (tp->vec_bytes > 2ull * DS4_TP_RDMA_MAX_MSG) { |
| tp_set_err(err, errlen, |
| "tp rdma: gate vector %llu bytes exceeds twice the driver's " |
| "%u message limit", |
| (unsigned long long)tp->vec_bytes, DS4_TP_RDMA_MAX_MSG); |
| return 0; |
| } |
| if (tp->vec_bytes > DS4_TP_RDMA_MAX_MSG) { |
| fprintf(stderr, |
| "ds4-tp: rdma gate vectors ride as 2 chunked messages " |
| "(%llu bytes > %u limit)\n", |
| (unsigned long long)tp->vec_bytes, DS4_TP_RDMA_MAX_MSG); |
| } |
| |
| |
| if (!tp_send_frame(tp->control_fd, DS4_TP_FRAME_RDMA_READY, NULL, 0)) { |
| tp_set_err(err, errlen, "tp rdma: ready send failed"); |
| return 0; |
| } |
| uint32_t rtype = 0, rbytes = 0; |
| if (!tp_read_frame_header(tp->control_fd, &rtype, &rbytes) || |
| rtype != DS4_TP_FRAME_RDMA_READY || rbytes != 0) { |
| tp_set_err(err, errlen, "tp rdma: ready barrier failed"); |
| return 0; |
| } |
| return 1; |
| } |
|
|
| |
| |
| static const char *tp_wc_status_str(int status) { |
| static char buf[32]; |
| snprintf(buf, sizeof(buf), "wc status %d", status); |
| return buf; |
| } |
|
|
| |
| |
| |
| static uint32_t tp_gate_slot(const ds4_tp *tp, uint64_t seq) { |
| if (tp->gates_per_token == 0) |
| return (uint32_t)((seq - 1) % tp->n_slots); |
| return tp->gate_slot_start + |
| (uint32_t)((seq - 1) % tp->gates_per_token) * tp->gate_slot_step; |
| } |
|
|
| |
| |
| |
| static int tp_rdma_drain_cq(ds4_tp *tp) { |
| ds4_tp_rdma *r = &tp->rdma; |
| struct ibv_wc wc[16]; |
| int n = ibv_poll_cq(r->cq, 16, wc); |
| if (n < 0) return 0; |
| for (int i = 0; i < n; i++) { |
| if (wc[i].status != IBV_WC_SUCCESS) { |
| fprintf(stderr, "ds4-tp: rdma completion error: %s (wr_id %llu)\n", |
| tp_wc_status_str(wc[i].status), |
| (unsigned long long)wc[i].wr_id); |
| return 0; |
| } |
| if (wc[i].opcode & IBV_WC_RECV) { |
| if (wc[i].wr_id > r->recv_done) r->recv_done = wc[i].wr_id; |
| } else if (r->send_outstanding > 0) { |
| r->send_outstanding--; |
| } |
| } |
| return 1; |
| } |
|
|
| |
| |
| |
| static int tp_rdma_post_gate_recv(ds4_tp *tp, uint64_t seq) { |
| ds4_tp_rdma *r = &tp->rdma; |
| const uint32_t slot = tp_gate_slot(tp, seq); |
| const uintptr_t base = |
| (uintptr_t)(tp->slab + tp->in_off + (uint64_t)slot * tp->vec_bytes); |
| |
| |
| |
| |
| |
| uint64_t off = 0; |
| while (off < tp->vec_bytes) { |
| const uint64_t len = tp->vec_bytes - off > DS4_TP_RDMA_MAX_MSG ? |
| DS4_TP_RDMA_MAX_MSG : tp->vec_bytes - off; |
| const int last = off + len == tp->vec_bytes; |
| struct ibv_sge sge; |
| struct ibv_recv_wr wr, *bad = NULL; |
| memset(&wr, 0, sizeof(wr)); |
| sge.addr = base + off; |
| sge.length = (uint32_t)len; |
| sge.lkey = r->mr->lkey; |
| wr.wr_id = last ? seq : 0; |
| wr.sg_list = &sge; |
| wr.num_sge = 1; |
| if (ibv_post_recv(r->qp, &wr, &bad) != 0) { |
| fprintf(stderr, "ds4-tp: rdma post_recv(seq %llu off %llu): %s\n", |
| (unsigned long long)seq, (unsigned long long)off, |
| strerror(errno)); |
| return 0; |
| } |
| off += len; |
| } |
| return 1; |
| } |
|
|
| |
| |
| static int tp_rdma_gate_exchange(ds4_tp *tp, uint32_t layer, uint32_t gate, uint64_t seq) { |
| ds4_tp_rdma *r = &tp->rdma; |
| const uint32_t slot = layer * DS4_TP_GATES_PER_LAYER + gate; |
| if (getenv("DS4_TP_GATE_TRACE")) { |
| fprintf(stderr, "ds4-tp: gate trace l=%u g=%u seq=%llu want_slot=%u\n", |
| layer, gate, (unsigned long long)seq, tp_gate_slot(tp, seq)); |
| } |
| if (slot != tp_gate_slot(tp, seq)) { |
| fprintf(stderr, "ds4-tp: gate order broke: layer %u gate %u vs seq %llu\n", |
| layer, gate, (unsigned long long)seq); |
| return 0; |
| } |
| const uintptr_t send_base = |
| (uintptr_t)(tp->slab + tp->out_off + (uint64_t)slot * tp->vec_bytes); |
| pthread_mutex_lock(&r->post_lock); |
| int ok = 1; |
| if (!r->recv_window_active) { |
| for (uint64_t s = seq; ok && s < seq + DS4_TP_RDMA_RECV_WINDOW; s++) |
| ok = tp_rdma_post_gate_recv(tp, s); |
| if (ok) r->recv_window_active = true; |
| } |
| for (uint64_t off = 0; ok && off < tp->vec_bytes; ) { |
| const uint64_t len = tp->vec_bytes - off > DS4_TP_RDMA_MAX_MSG ? |
| DS4_TP_RDMA_MAX_MSG : tp->vec_bytes - off; |
| struct ibv_sge sge; |
| struct ibv_send_wr wr, *bad = NULL; |
| memset(&wr, 0, sizeof(wr)); |
| sge.addr = send_base + off; |
| sge.length = (uint32_t)len; |
| sge.lkey = r->mr->lkey; |
| wr.wr_id = seq; |
| wr.sg_list = &sge; |
| wr.num_sge = 1; |
| wr.opcode = IBV_WR_SEND; |
| wr.send_flags = IBV_SEND_SIGNALED; |
| ok = ibv_post_send(r->qp, &wr, &bad) == 0; |
| if (!ok) { |
| fprintf(stderr, "ds4-tp: rdma post_send: %s\n", strerror(errno)); |
| } else { |
| r->send_outstanding++; |
| } |
| off += len; |
| } |
|
|
| double deadline = 0.0; |
| uint32_t peer_poll = 0; |
| while (ok && r->recv_done < seq) { |
| ok = tp_rdma_drain_cq(tp); |
| if (ok && (peer_poll++ & 0x3fffu) == 0 && tp_peer_closed(tp)) { |
| fprintf(stderr, "ds4-tp: peer disconnected during RDMA gate\n"); |
| ok = 0; |
| } |
| if (deadline == 0.0) deadline = tp_now_sec() + (double)tp->timeout_sec; |
| else if (tp_now_sec() > deadline) { |
| fprintf(stderr, "ds4-tp: timeout waiting gate seq %llu (recv_done %llu)\n", |
| (unsigned long long)seq, (unsigned long long)r->recv_done); |
| ok = 0; |
| } |
| } |
| if (ok) ok = tp_rdma_post_gate_recv(tp, seq + DS4_TP_RDMA_RECV_WINDOW); |
| if (ok) r->last_gate_seq = seq; |
| pthread_mutex_unlock(&r->post_lock); |
| return ok; |
| } |
|
|
| static int tp_rdma_big_gate_capable(const ds4_tp *tp) { |
| const uint64_t stage_bytes = |
| (uint64_t)DS4_TP_RDMA_BULK_SLOTS * DS4_TP_RDMA_MAX_MSG; |
| const uint64_t batch_region_bytes = |
| (uint64_t)tp->n_layer * DS4_TP_BATCH_MAX_ROWS * tp->vec_bytes; |
| return tp->rdma.qp && tp->rdma.mr && batch_region_bytes >= stage_bytes; |
| } |
|
|
| |
| |
| |
| |
| static int tp_rdma_drain_decode_window(ds4_tp *tp) { |
| ds4_tp_rdma *r = &tp->rdma; |
| if (!r->recv_window_active) return 1; |
|
|
| const uint32_t chunks_per_gate = |
| (uint32_t)((tp->vec_bytes + DS4_TP_RDMA_MAX_MSG - 1u) / |
| DS4_TP_RDMA_MAX_MSG); |
| const uint32_t nwr = DS4_TP_RDMA_RECV_WINDOW * chunks_per_gate; |
| struct ibv_sge sge[DS4_TP_RDMA_RECV_WINDOW * 2u]; |
| struct ibv_send_wr wr[DS4_TP_RDMA_RECV_WINDOW * 2u]; |
| memset(wr, 0, sizeof(wr)); |
| uint8_t *scratch = tp->slab + tp->batch_out_off; |
| uint32_t wi = 0; |
| for (uint32_t gate = 0; gate < DS4_TP_RDMA_RECV_WINDOW; gate++) { |
| for (uint64_t off = 0; off < tp->vec_bytes; ) { |
| const uint64_t len = tp->vec_bytes - off > DS4_TP_RDMA_MAX_MSG ? |
| DS4_TP_RDMA_MAX_MSG : tp->vec_bytes - off; |
| sge[wi] = (struct ibv_sge) { |
| .addr = (uintptr_t)(scratch + off), |
| .length = (uint32_t)len, |
| .lkey = r->mr->lkey, |
| }; |
| wr[wi].wr_id = DS4_TP_RDMA_BULK_WR_TAG | ((uint64_t)wi + 1u); |
| wr[wi].sg_list = &sge[wi]; |
| wr[wi].num_sge = 1; |
| wr[wi].opcode = IBV_WR_SEND; |
| wr[wi].send_flags = wi + 1u == nwr ? IBV_SEND_SIGNALED : 0; |
| if (wi > 0) wr[wi - 1u].next = &wr[wi]; |
| wi++; |
| off += len; |
| } |
| } |
|
|
| pthread_mutex_lock(&r->post_lock); |
| struct ibv_send_wr *bad = NULL; |
| if (ibv_post_send(r->qp, wr, &bad) != 0) { |
| fprintf(stderr, "ds4-tp: rdma receive-window drain post failed: %s\n", |
| strerror(errno)); |
| pthread_mutex_unlock(&r->post_lock); |
| return 0; |
| } |
|
|
| uint32_t recv_done = 0; |
| int send_done = 0; |
| const double deadline = tp_now_sec() + (double)tp->timeout_sec; |
| uint32_t peer_poll = 0; |
| while (recv_done < nwr || !send_done) { |
| struct ibv_wc wc[DS4_TP_RDMA_RECV_WINDOW * 2u + 1u]; |
| int n = ibv_poll_cq(r->cq, |
| (int)(DS4_TP_RDMA_RECV_WINDOW * 2u + 1u), wc); |
| if (n < 0) { |
| pthread_mutex_unlock(&r->post_lock); |
| return 0; |
| } |
| for (int i = 0; i < n; i++) { |
| if (wc[i].status != IBV_WC_SUCCESS) { |
| fprintf(stderr, "ds4-tp: rdma receive-window drain: %s\n", |
| tp_wc_status_str(wc[i].status)); |
| pthread_mutex_unlock(&r->post_lock); |
| return 0; |
| } |
| if (wc[i].opcode & IBV_WC_RECV) { |
| recv_done++; |
| } else if (wc[i].wr_id & DS4_TP_RDMA_BULK_WR_TAG) { |
| send_done = 1; |
| } else if (r->send_outstanding > 0) { |
| r->send_outstanding--; |
| } |
| } |
| if ((peer_poll++ & 0x3fffu) == 0 && tp_peer_closed(tp)) { |
| fprintf(stderr, |
| "ds4-tp: peer disconnected while draining RDMA receives\n"); |
| pthread_mutex_unlock(&r->post_lock); |
| return 0; |
| } |
| if (tp_now_sec() > deadline) { |
| fprintf(stderr, |
| "ds4-tp: timeout draining RDMA receive window (%u/%u)\n", |
| recv_done, nwr); |
| pthread_mutex_unlock(&r->post_lock); |
| return 0; |
| } |
| } |
| r->recv_done = r->last_gate_seq; |
| r->recv_window_active = false; |
| pthread_mutex_unlock(&r->post_lock); |
| return 1; |
| } |
|
|
| |
| |
| |
| |
| static int tp_rdma_big_gate_exchange(ds4_tp *tp, |
| const void *out, |
| void *in, |
| uint64_t bytes) { |
| ds4_tp_rdma *r = &tp->rdma; |
| if (!tp_rdma_big_gate_capable(tp) || r->recv_window_active) return 0; |
|
|
| |
| |
| |
| |
| const uintptr_t slab_lo = (uintptr_t)tp->slab; |
| const uintptr_t slab_hi = slab_lo + tp->slab_bytes; |
| const uintptr_t out_lo = (uintptr_t)out; |
| const uintptr_t in_lo = (uintptr_t)in; |
| const bool direct = |
| out_lo >= slab_lo && out_lo <= slab_hi && bytes <= slab_hi - out_lo && |
| in_lo >= slab_lo && in_lo <= slab_hi && bytes <= slab_hi - in_lo; |
| uint8_t *stage_send = tp->slab + tp->batch_out_off; |
| uint8_t *stage_recv = tp->slab + tp->batch_in_off; |
| uint64_t off = 0; |
| while (off < bytes) { |
| const uint64_t remaining = bytes - off; |
| uint32_t chunks = (uint32_t)((remaining + DS4_TP_RDMA_MAX_MSG - 1u) / |
| DS4_TP_RDMA_MAX_MSG); |
| if (chunks > DS4_TP_RDMA_BULK_SLOTS) |
| chunks = DS4_TP_RDMA_BULK_SLOTS; |
|
|
| uint32_t lens[DS4_TP_RDMA_BULK_SLOTS]; |
| uint64_t chunk_off[DS4_TP_RDMA_BULK_SLOTS]; |
| uint64_t round_bytes = 0; |
| for (uint32_t i = 0; i < chunks; i++) { |
| const uint64_t left = remaining - round_bytes; |
| lens[i] = (uint32_t)(left > DS4_TP_RDMA_MAX_MSG ? |
| DS4_TP_RDMA_MAX_MSG : left); |
| chunk_off[i] = direct ? round_bytes : |
| (uint64_t)i * DS4_TP_RDMA_MAX_MSG; |
| if (!direct) { |
| memcpy(stage_send + chunk_off[i], |
| (const uint8_t *)out + off + round_bytes, lens[i]); |
| } |
| round_bytes += lens[i]; |
| } |
|
|
| struct ibv_sge recv_sge[DS4_TP_RDMA_BULK_SLOTS]; |
| struct ibv_recv_wr recv_wr[DS4_TP_RDMA_BULK_SLOTS]; |
| memset(recv_wr, 0, sizeof(recv_wr)); |
| for (uint32_t i = 0; i < chunks; i++) { |
| recv_sge[i] = (struct ibv_sge) { |
| .addr = direct ? in_lo + off + chunk_off[i] : |
| (uintptr_t)(stage_recv + chunk_off[i]), |
| .length = lens[i], |
| .lkey = r->mr->lkey, |
| }; |
| recv_wr[i].wr_id = DS4_TP_RDMA_BULK_WR_TAG | ((uint64_t)i + 1u); |
| recv_wr[i].sg_list = &recv_sge[i]; |
| recv_wr[i].num_sge = 1; |
| recv_wr[i].next = i + 1u < chunks ? &recv_wr[i + 1u] : NULL; |
| } |
| struct ibv_recv_wr *bad_recv = NULL; |
| if (ibv_post_recv(r->qp, recv_wr, &bad_recv) != 0) { |
| fprintf(stderr, "ds4-tp: bulk rdma post_recv: %s\n", |
| strerror(errno)); |
| return 0; |
| } |
| atomic_thread_fence(memory_order_release); |
| struct ibv_sge send_sge[DS4_TP_RDMA_BULK_SLOTS]; |
| struct ibv_send_wr send_wr[DS4_TP_RDMA_BULK_SLOTS]; |
| memset(send_wr, 0, sizeof(send_wr)); |
| for (uint32_t i = 0; i < chunks; i++) { |
| send_sge[i] = (struct ibv_sge) { |
| .addr = direct ? out_lo + off + chunk_off[i] : |
| (uintptr_t)(stage_send + chunk_off[i]), |
| .length = lens[i], |
| .lkey = r->mr->lkey, |
| }; |
| send_wr[i].wr_id = DS4_TP_RDMA_BULK_WR_TAG | ((uint64_t)i + 1u); |
| send_wr[i].sg_list = &send_sge[i]; |
| send_wr[i].num_sge = 1; |
| send_wr[i].opcode = IBV_WR_SEND; |
| send_wr[i].send_flags = i + 1u == chunks ? IBV_SEND_SIGNALED : 0; |
| send_wr[i].next = i + 1u < chunks ? &send_wr[i + 1u] : NULL; |
| } |
| struct ibv_send_wr *bad_send = NULL; |
| if (ibv_post_send(r->qp, send_wr, &bad_send) != 0) { |
| fprintf(stderr, "ds4-tp: bulk rdma post_send: %s\n", |
| strerror(errno)); |
| return 0; |
| } |
|
|
| uint32_t recv_done = 0; |
| int send_done = 0; |
| const double deadline = tp_now_sec() + (double)tp->timeout_sec; |
| uint32_t peer_poll = 0; |
| while (recv_done < chunks || !send_done) { |
| struct ibv_wc wc[DS4_TP_RDMA_BULK_SLOTS + 1u]; |
| int n = ibv_poll_cq(r->cq, |
| (int)(DS4_TP_RDMA_BULK_SLOTS + 1u), wc); |
| if (n < 0) return 0; |
| for (int i = 0; i < n; i++) { |
| if (wc[i].status != IBV_WC_SUCCESS) { |
| fprintf(stderr, |
| "ds4-tp: bulk rdma completion error: %s\n", |
| tp_wc_status_str(wc[i].status)); |
| return 0; |
| } |
| if ((wc[i].wr_id & DS4_TP_RDMA_BULK_WR_TAG) == 0) { |
| |
| |
| if (wc[i].opcode & IBV_WC_RECV) { |
| if (wc[i].wr_id > r->recv_done) |
| r->recv_done = wc[i].wr_id; |
| } else if (r->send_outstanding > 0) { |
| r->send_outstanding--; |
| } |
| continue; |
| } |
| if (wc[i].opcode & IBV_WC_RECV) recv_done++; |
| else send_done = 1; |
| } |
| if ((peer_poll++ & 0x3fffu) == 0 && tp_peer_closed(tp)) { |
| fprintf(stderr, |
| "ds4-tp: peer disconnected during bulk RDMA gate\n"); |
| return 0; |
| } |
| if (tp_now_sec() > deadline) { |
| fprintf(stderr, |
| "ds4-tp: timeout waiting for bulk RDMA round " |
| "(%u/%u recvs, send=%d)\n", |
| recv_done, chunks, send_done); |
| return 0; |
| } |
| } |
| atomic_thread_fence(memory_order_acquire); |
| if (!direct) { |
| round_bytes = 0; |
| for (uint32_t i = 0; i < chunks; i++) { |
| memcpy((uint8_t *)in + off + round_bytes, |
| stage_recv + chunk_off[i], lens[i]); |
| round_bytes += lens[i]; |
| } |
| } |
| off += round_bytes; |
| } |
| return 1; |
| } |
|
|
| static void tp_rdma_close(ds4_tp *tp) { |
| ds4_tp_rdma *r = &tp->rdma; |
| if (r->qp) r->api.destroy_qp(r->qp); |
| if (r->mr) r->api.dereg_mr(r->mr); |
| if (r->cq) r->api.destroy_cq(r->cq); |
| if (r->pd) r->api.dealloc_pd(r->pd); |
| if (r->ctx) r->api.close_device(r->ctx); |
| r->qp = NULL; r->mr = NULL; r->cq = NULL; r->pd = NULL; r->ctx = NULL; |
| } |
|
|
| #endif |
|
|
| |
| |
| |
|
|
| static int tp_hello_exchange(ds4_tp *tp, const ds4_tp_identity *id, int rdma_ok, |
| char *err, size_t errlen) { |
| ds4_tp_hello_fixed mine = { |
| .magic = DS4_TP_MAGIC, |
| .version = DS4_TP_PROTOCOL_VERSION, |
| .role = (uint32_t)tp->opt.role, |
| .rdma_ok = (uint32_t)rdma_ok, |
| .gguf_bytes = id->gguf_bytes, |
| .model_id = id->model_id, |
| .n_layer = id->n_layer, |
| .n_embd = id->n_embd, |
| .n_vocab = id->n_vocab, |
| .quant_bits = id->quant_bits, |
| .ctx_size = id->ctx_size, |
| .gate_slot_start = id->gate_slot_start, |
| .gate_slot_step = id->gate_slot_step, |
| .gates_per_token = id->gates_per_token, |
| }; |
| ds4_tp_hello_fixed theirs; |
| if (!tp_write_full(tp->control_fd, &mine, sizeof(mine)) || |
| !tp_read_full(tp->control_fd, &theirs, sizeof(theirs))) { |
| tp_set_err(err, errlen, "tp hello exchange failed"); |
| return 0; |
| } |
| if (theirs.magic != DS4_TP_MAGIC) { |
| tp_set_err(err, errlen, "tp hello: bad magic (mixed byte order or wrong peer?)"); |
| return 0; |
| } |
| if (theirs.version != DS4_TP_PROTOCOL_VERSION) { |
| tp_set_err(err, errlen, "tp hello: protocol version %u != %u", |
| theirs.version, DS4_TP_PROTOCOL_VERSION); |
| return 0; |
| } |
| if (theirs.role == mine.role) { |
| tp_set_err(err, errlen, "tp hello: both sides claim role %u", mine.role); |
| return 0; |
| } |
| if (theirs.gguf_bytes != mine.gguf_bytes || theirs.model_id != mine.model_id || |
| theirs.n_layer != mine.n_layer || theirs.n_embd != mine.n_embd || |
| theirs.n_vocab != mine.n_vocab || theirs.quant_bits != mine.quant_bits || |
| theirs.gate_slot_start != mine.gate_slot_start || |
| theirs.gate_slot_step != mine.gate_slot_step || |
| theirs.gates_per_token != mine.gates_per_token) { |
| tp_set_err(err, errlen, |
| "tp hello: model mismatch (peer gguf=%llu id=%u layers=%u embd=%u " |
| "vocab=%u qbits=%u)", |
| (unsigned long long)theirs.gguf_bytes, theirs.model_id, |
| theirs.n_layer, theirs.n_embd, theirs.n_vocab, theirs.quant_bits); |
| return 0; |
| } |
| tp->peer_ctx = theirs.ctx_size; |
| tp->n_layer = id->n_layer; |
| tp->n_embd = id->n_embd; |
| tp->vec_bytes = (uint64_t)id->n_embd * sizeof(float); |
| tp->n_slots = id->n_layer * DS4_TP_GATES_PER_LAYER; |
| tp->gate_slot_start = id->gate_slot_start; |
| tp->gate_slot_step = id->gate_slot_step; |
| tp->gates_per_token = id->gates_per_token; |
| tp_slab_layout(tp); |
| |
| int want_rdma = tp->opt.transport != DS4_TP_TRANSPORT_TCP; |
| tp->rdma_active = want_rdma && rdma_ok && theirs.rdma_ok; |
| if (tp->opt.transport == DS4_TP_TRANSPORT_RDMA && !tp->rdma_active) { |
| tp_set_err(err, errlen, "tp: --transport rdma but %s side has no active device", |
| rdma_ok ? "the peer" : "this"); |
| return 0; |
| } |
| return 1; |
| } |
|
|
| int ds4_tp_create( |
| ds4_tp **out, |
| const ds4_tp_options *opt, |
| const ds4_tp_identity *id, |
| char *err, |
| size_t errlen) |
| { |
| *out = NULL; |
| ds4_tp *tp = calloc(1, sizeof(*tp)); |
| if (!tp) { |
| tp_set_err(err, errlen, "tp: out of memory"); |
| return 0; |
| } |
| tp->opt = *opt; |
| tp->rank = opt->role == DS4_TP_LEADER ? 0 : 1; |
| tp->control_fd = -1; |
| tp->data_fd = -1; |
| tp->timeout_sec = DS4_TP_DEFAULT_TIMEOUT_SEC; |
| const char *tmo = getenv("DS4_TP_TIMEOUT_SEC"); |
| if (tmo) tp->timeout_sec = (uint64_t)atoi(tmo); |
|
|
| int rdma_ok = 0; |
| #ifdef DS4_TP_HAVE_VERBS |
| if (opt->transport != DS4_TP_TRANSPORT_TCP && |
| (uint64_t)id->n_embd * sizeof(float) <= 2ull * DS4_TP_RDMA_MAX_MSG) |
| rdma_ok = tp_rdma_probe(&tp->rdma.api); |
| #endif |
|
|
| int listener = -1; |
| if (tp->rank == 0) { |
| listener = tp_listen(opt->listen_host, opt->listen_port, err, errlen); |
| if (listener < 0) goto fail; |
| fprintf(stderr, "ds4-tp: waiting for worker on %s:%d ...\n", |
| opt->listen_host ? opt->listen_host : "0.0.0.0", opt->listen_port); |
| tp->control_fd = accept(listener, NULL, NULL); |
| if (tp->control_fd < 0) { |
| tp_set_err(err, errlen, "tp accept: %s", strerror(errno)); |
| goto fail; |
| } |
| } else { |
| tp->control_fd = tp_dial(opt->leader_host, opt->leader_port, |
| (double)tp->timeout_sec, err, errlen); |
| if (tp->control_fd < 0) goto fail; |
| } |
| tp_socket_tune(tp->control_fd); |
|
|
| if (!tp_hello_exchange(tp, id, rdma_ok, err, errlen)) goto fail; |
|
|
| #ifdef DS4_TP_HAVE_VERBS |
| if (tp->rdma_active) { |
| if (!tp_rdma_open(tp, err, errlen)) goto fail; |
| } |
| #endif |
| { |
| |
| |
| |
| if (tp->rank == 0) { |
| tp->data_fd = accept(listener, NULL, NULL); |
| if (tp->data_fd < 0) { |
| tp_set_err(err, errlen, "tp data accept: %s", strerror(errno)); |
| goto fail; |
| } |
| } else { |
| tp->data_fd = tp_dial(opt->leader_host, opt->leader_port, |
| (double)tp->timeout_sec, err, errlen); |
| if (tp->data_fd < 0) goto fail; |
| } |
| tp_socket_tune(tp->data_fd); |
| } |
| if (listener >= 0) close(listener); |
| fprintf(stderr, "ds4-tp: %s connected, transport=%s\n", |
| tp->rank == 0 ? "worker" : "leader", |
| tp->rdma_active ? "rdma" : "tcp"); |
| *out = tp; |
| return 1; |
| fail: |
| if (listener >= 0) close(listener); |
| ds4_tp_free(tp); |
| return 0; |
| } |
|
|
| int ds4_tp_attach_slab(ds4_tp *tp, void *base, char *err, size_t errlen) { |
| tp->slab = base; |
| memset(tp->slab + tp->in_flags_off, 0, (uint64_t)tp->n_slots * 8); |
| memset(tp->slab + tp->token_off, 0, 16); |
| #ifdef DS4_TP_HAVE_VERBS |
| if (tp->rdma_active) return tp_rdma_register_and_exchange(tp, err, errlen); |
| #endif |
| (void)err; (void)errlen; |
| return 1; |
| } |
|
|
| void ds4_tp_free(ds4_tp *tp) { |
| if (!tp) return; |
| #ifdef DS4_TP_HAVE_VERBS |
| tp_rdma_close(tp); |
| #endif |
| if (tp->control_fd >= 0) close(tp->control_fd); |
| if (tp->data_fd >= 0) close(tp->data_fd); |
| free(tp); |
| } |
|
|
| int ds4_tp_rank(const ds4_tp *tp) { return tp->rank; } |
| bool ds4_tp_is_rdma(const ds4_tp *tp) { return tp->rdma_active; } |
| uint32_t ds4_tp_peer_ctx(const ds4_tp *tp) { return tp->peer_ctx; } |
| bool ds4_tp_failed(const ds4_tp *tp) { |
| return tp && atomic_load_explicit(&tp->failed, memory_order_acquire); |
| } |
| void ds4_tp_mark_failed(ds4_tp *tp) { |
| if (tp) atomic_store_explicit(&tp->failed, true, memory_order_release); |
| } |
|
|
| |
| |
| |
|
|
| int ds4_tp_gate_exchange(ds4_tp *tp, uint32_t layer, uint32_t gate, uint64_t seq) { |
| #ifdef DS4_TP_HAVE_VERBS |
| if (tp->rdma_active) return tp_rdma_gate_exchange(tp, layer, gate, seq); |
| #endif |
| |
| |
| |
| |
| ds4_tp_gate_header h = { DS4_TP_MAGIC, (uint16_t)layer, (uint16_t)gate, seq }; |
| struct iovec iov[2] = { |
| { &h, sizeof(h) }, |
| { tp->slab + ds4_tp_slab_out_offset(tp, layer, gate), tp->vec_bytes }, |
| }; |
| size_t want = sizeof(h) + tp->vec_bytes; |
| ssize_t w = writev(tp->data_fd, iov, 2); |
| if (w < 0 || (size_t)w != want) { |
| |
| if (w < 0) return 0; |
| size_t done = (size_t)w; |
| if (done < sizeof(h)) { |
| if (!tp_write_full(tp->data_fd, (char *)&h + done, sizeof(h) - done)) return 0; |
| done = sizeof(h); |
| } |
| uint64_t payload_done = done - sizeof(h); |
| if (!tp_write_full(tp->data_fd, |
| tp->slab + ds4_tp_slab_out_offset(tp, layer, gate) + payload_done, |
| tp->vec_bytes - payload_done)) |
| return 0; |
| } |
| ds4_tp_gate_header ph; |
| if (!tp_read_full(tp->data_fd, &ph, sizeof(ph))) return 0; |
| if (ph.magic != DS4_TP_MAGIC || ph.layer != layer || ph.gate != gate || ph.seq != seq) { |
| fprintf(stderr, "ds4-tp: gate desync: got l=%u g=%u seq=%llu, want l=%u g=%u seq=%llu\n", |
| ph.layer, ph.gate, (unsigned long long)ph.seq, |
| layer, gate, (unsigned long long)seq); |
| return 0; |
| } |
| if (!tp_read_full(tp->data_fd, tp->slab + ds4_tp_slab_in_offset(tp, layer, gate), |
| tp->vec_bytes)) |
| return 0; |
| return 1; |
| } |
|
|
| |
| |
| |
| int ds4_tp_batch_gate_exchange(ds4_tp *tp, uint32_t layer, uint32_t rows, |
| uint64_t seq) { |
| if (tp->data_fd < 0 || rows == 0 || rows > DS4_TP_BATCH_MAX_ROWS) return 0; |
| const uint64_t bytes = (uint64_t)rows * tp->vec_bytes; |
| ds4_tp_gate_header h = { DS4_TP_BATCH_MAGIC, (uint16_t)layer, |
| (uint16_t)rows, seq }; |
| #ifdef DS4_TP_HAVE_VERBS |
| if (tp->rdma_active && tp_rdma_big_gate_capable(tp)) { |
| if (!tp_write_full(tp->data_fd, &h, sizeof(h))) return 0; |
| ds4_tp_gate_header ph; |
| if (!tp_read_full(tp->data_fd, &ph, sizeof(ph))) return 0; |
| if (ph.magic != DS4_TP_BATCH_MAGIC || ph.layer != layer || |
| ph.gate != rows || ph.seq != seq) { |
| fprintf(stderr, |
| "ds4-tp: batch gate desync: got l=%u rows=%u seq=%llu, " |
| "want l=%u rows=%u seq=%llu\n", |
| ph.layer, ph.gate, (unsigned long long)ph.seq, |
| layer, rows, (unsigned long long)seq); |
| return 0; |
| } |
| if (!tp_rdma_drain_decode_window(tp)) return 0; |
| return tp_rdma_big_gate_exchange( |
| tp, |
| tp->slab + ds4_tp_slab_batch_out_offset(tp, layer), |
| tp->slab + ds4_tp_slab_batch_in_offset(tp, layer), |
| bytes); |
| } |
| #endif |
| struct iovec iov[2] = { |
| { &h, sizeof(h) }, |
| { tp->slab + ds4_tp_slab_batch_out_offset(tp, layer), bytes }, |
| }; |
| size_t want = sizeof(h) + bytes; |
| ssize_t w = writev(tp->data_fd, iov, 2); |
| if (w < 0) return 0; |
| if ((size_t)w != want) { |
| size_t done = (size_t)w; |
| if (done < sizeof(h)) { |
| if (!tp_write_full(tp->data_fd, (char *)&h + done, sizeof(h) - done)) |
| return 0; |
| done = sizeof(h); |
| } |
| uint64_t payload_done = done - sizeof(h); |
| if (!tp_write_full(tp->data_fd, |
| tp->slab + ds4_tp_slab_batch_out_offset(tp, layer) + |
| payload_done, |
| bytes - payload_done)) |
| return 0; |
| } |
| ds4_tp_gate_header ph; |
| if (!tp_read_full(tp->data_fd, &ph, sizeof(ph))) return 0; |
| if (ph.magic != DS4_TP_BATCH_MAGIC || ph.layer != layer || |
| ph.gate != rows || ph.seq != seq) { |
| fprintf(stderr, |
| "ds4-tp: batch gate desync: got l=%u rows=%u seq=%llu, " |
| "want l=%u rows=%u seq=%llu\n", |
| ph.layer, ph.gate, (unsigned long long)ph.seq, |
| layer, rows, (unsigned long long)seq); |
| return 0; |
| } |
| return tp_read_full(tp->data_fd, |
| tp->slab + ds4_tp_slab_batch_in_offset(tp, layer), |
| bytes); |
| } |
|
|
| |
| |
| |
| |
| #define DS4_TP_BIG_CHUNK (2ull * 1024ull * 1024ull) |
|
|
| int ds4_tp_big_gate_exchange(ds4_tp *tp, uint32_t layer, uint64_t seq, |
| const void *out, void *in, uint64_t bytes) { |
| if (tp->data_fd < 0 || !out || !in || bytes == 0) return 0; |
| ds4_tp_gate_header h = { DS4_TP_BATCH_MAGIC, (uint16_t)layer, 0xB16u, seq }; |
| if (!tp_write_full(tp->data_fd, &h, sizeof(h))) return 0; |
| ds4_tp_gate_header ph; |
| if (!tp_read_full(tp->data_fd, &ph, sizeof(ph))) return 0; |
| if (ph.magic != DS4_TP_BATCH_MAGIC || ph.layer != layer || |
| ph.gate != 0xB16u || ph.seq != seq) { |
| fprintf(stderr, |
| "ds4-tp: big gate desync: got l=%u tag=%x seq=%llu, want l=%u seq=%llu\n", |
| ph.layer, ph.gate, (unsigned long long)ph.seq, |
| layer, (unsigned long long)seq); |
| return 0; |
| } |
| #ifdef DS4_TP_HAVE_VERBS |
| if (tp->rdma_active && tp_rdma_big_gate_capable(tp)) { |
| if (!tp_rdma_drain_decode_window(tp)) return 0; |
| return tp_rdma_big_gate_exchange(tp, out, in, bytes); |
| } |
| #endif |
| uint64_t off = 0; |
| while (off < bytes) { |
| const uint64_t n = bytes - off > DS4_TP_BIG_CHUNK ? |
| DS4_TP_BIG_CHUNK : bytes - off; |
| if (!tp_write_full(tp->data_fd, (const char *)out + off, n)) return 0; |
| if (!tp_read_full(tp->data_fd, (char *)in + off, n)) return 0; |
| off += n; |
| } |
| if (getenv("DS4_GLM_TP_DEBUG")) { |
| const float *o = (const float *)out, *i = (const float *)in; |
| fprintf(stderr, |
| "ds4-tp: big gate l=%u seq=%llu out[0..3]=%g %g %g %g in[0..3]=%g %g %g %g\n", |
| layer, (unsigned long long)seq, |
| o[0], o[1], o[2], o[3], i[0], i[1], i[2], i[3]); |
| } |
| return 1; |
| } |
|
|
| |
| |
| |
|
|
| typedef struct { |
| uint64_t session_id; |
| uint32_t count; |
| uint32_t reserved; |
| } ds4_tp_token_command_header; |
|
|
| typedef struct { |
| uint64_t session_id; |
| int32_t value; |
| uint32_t reserved; |
| } ds4_tp_value_command; |
|
|
| typedef struct { |
| uint64_t session_id; |
| uint64_t seq; |
| int32_t token; |
| uint32_t reserved; |
| } ds4_tp_eval_command; |
|
|
| typedef struct { |
| uint32_t count; |
| uint32_t reserved; |
| } ds4_tp_batch_command_header; |
|
|
| typedef struct { |
| uint64_t prefill_session_id; |
| uint32_t prompt_count; |
| uint32_t item_count; |
| } ds4_tp_mixed_command_header; |
|
|
| typedef struct { |
| uint64_t session_id; |
| int32_t status; |
| uint32_t reserved; |
| } ds4_tp_command_ack; |
|
|
| static int tp_send_token_command(ds4_tp *tp, uint32_t type, |
| uint64_t session_id, const int *tokens, |
| uint32_t count) { |
| const uint64_t bytes64 = sizeof(ds4_tp_token_command_header) + |
| (uint64_t)count * sizeof(int32_t); |
| if (!tp || (!tokens && count != 0) || bytes64 > UINT32_MAX) return 0; |
| const uint32_t bytes = (uint32_t)bytes64; |
| uint8_t *payload = malloc(bytes ? bytes : 1u); |
| if (!payload) return 0; |
| ds4_tp_token_command_header h = { session_id, count, 0 }; |
| memcpy(payload, &h, sizeof(h)); |
| int32_t *wire_tokens = (int32_t *)(payload + sizeof(h)); |
| for (uint32_t i = 0; i < count; i++) wire_tokens[i] = (int32_t)tokens[i]; |
| const int ok = tp_send_frame(tp->control_fd, type, payload, bytes); |
| free(payload); |
| return ok; |
| } |
|
|
| int ds4_tp_send_session_create(ds4_tp *tp, uint64_t session_id, int ctx_size) { |
| ds4_tp_value_command msg = { session_id, (int32_t)ctx_size, 0 }; |
| return tp_send_frame(tp->control_fd, DS4_TP_FRAME_SESSION_CREATE, |
| &msg, sizeof(msg)); |
| } |
|
|
| int ds4_tp_send_session_destroy(ds4_tp *tp, uint64_t session_id) { |
| return tp_send_frame(tp->control_fd, DS4_TP_FRAME_SESSION_DESTROY, |
| &session_id, sizeof(session_id)); |
| } |
|
|
| int ds4_tp_send_sync(ds4_tp *tp, uint64_t session_id, |
| const int *tokens, uint32_t n_tokens) { |
| return tp_send_token_command(tp, DS4_TP_FRAME_SYNC, session_id, |
| tokens, n_tokens); |
| } |
|
|
| int ds4_tp_send_eval(ds4_tp *tp, uint64_t session_id, |
| uint64_t seq, int token) { |
| ds4_tp_eval_command msg = { session_id, seq, (int32_t)token, 0 }; |
| return tp_send_frame(tp->control_fd, DS4_TP_FRAME_EVAL, &msg, sizeof(msg)); |
| } |
|
|
| int ds4_tp_send_rewind(ds4_tp *tp, uint64_t session_id, int pos) { |
| ds4_tp_value_command msg = { session_id, (int32_t)pos, 0 }; |
| return tp_send_frame(tp->control_fd, DS4_TP_FRAME_REWIND, |
| &msg, sizeof(msg)); |
| } |
|
|
| int ds4_tp_send_invalidate(ds4_tp *tp, uint64_t session_id) { |
| return tp_send_frame(tp->control_fd, DS4_TP_FRAME_INVALIDATE, |
| &session_id, sizeof(session_id)); |
| } |
|
|
| int ds4_tp_send_eval_batch(ds4_tp *tp, const ds4_tp_batch_item *items, |
| uint32_t count) { |
| const uint64_t bytes64 = sizeof(ds4_tp_batch_command_header) + |
| (uint64_t)count * sizeof(*items); |
| if (!tp || !items || count == 0 || bytes64 > UINT32_MAX) return 0; |
| const uint32_t bytes = (uint32_t)bytes64; |
| uint8_t *payload = malloc(bytes); |
| if (!payload) return 0; |
| ds4_tp_batch_command_header h = { count, 0 }; |
| memcpy(payload, &h, sizeof(h)); |
| memcpy(payload + sizeof(h), items, (size_t)count * sizeof(*items)); |
| const int ok = tp_send_frame(tp->control_fd, DS4_TP_FRAME_EVAL_BATCH, |
| payload, bytes); |
| free(payload); |
| return ok; |
| } |
|
|
| int ds4_tp_send_mixed_batch(ds4_tp *tp, uint64_t prefill_session_id, |
| const int *prompt, uint32_t prompt_count, |
| const ds4_tp_batch_item *items, |
| uint32_t count) { |
| const uint64_t prompt_bytes = (uint64_t)prompt_count * sizeof(int32_t); |
| const uint64_t item_bytes = (uint64_t)count * sizeof(*items); |
| const uint64_t bytes64 = sizeof(ds4_tp_mixed_command_header) + |
| prompt_bytes + item_bytes; |
| if (!tp || !prompt || prompt_count == 0 || !items || count == 0 || |
| bytes64 > UINT32_MAX) return 0; |
| const uint32_t bytes = (uint32_t)bytes64; |
| uint8_t *payload = malloc(bytes); |
| if (!payload) return 0; |
| ds4_tp_mixed_command_header h = { |
| prefill_session_id, prompt_count, count |
| }; |
| memcpy(payload, &h, sizeof(h)); |
| int32_t *wire_tokens = (int32_t *)(payload + sizeof(h)); |
| for (uint32_t i = 0; i < prompt_count; i++) { |
| wire_tokens[i] = (int32_t)prompt[i]; |
| } |
| memcpy(payload + sizeof(h) + prompt_bytes, items, (size_t)item_bytes); |
| const int ok = tp_send_frame(tp->control_fd, DS4_TP_FRAME_MIXED_BATCH, |
| payload, bytes); |
| free(payload); |
| return ok; |
| } |
|
|
| int ds4_tp_send_command_ack(ds4_tp *tp, uint64_t session_id, int status) { |
| ds4_tp_command_ack ack = { session_id, (int32_t)status, 0 }; |
| return tp_send_frame(tp->control_fd, DS4_TP_FRAME_COMMAND_ACK, |
| &ack, sizeof(ack)); |
| } |
|
|
| int ds4_tp_wait_command_ack(ds4_tp *tp, uint64_t session_id, |
| const char *operation, char *err, size_t errlen) { |
| uint32_t type = 0, bytes = 0; |
| ds4_tp_command_ack ack; |
| if (!tp_read_frame_header(tp->control_fd, &type, &bytes) || |
| type != DS4_TP_FRAME_COMMAND_ACK || bytes != sizeof(ack) || |
| !tp_read_full(tp->control_fd, &ack, sizeof(ack))) { |
| ds4_tp_mark_failed(tp); |
| tp_set_err(err, errlen, "tp: worker failed during %s", |
| operation ? operation : "command"); |
| return 0; |
| } |
| if (ack.session_id != session_id || ack.status != 0) { |
| tp_set_err(err, errlen, |
| "tp: worker %s failed (session %llu, status %d)", |
| operation ? operation : "command", |
| (unsigned long long)ack.session_id, (int)ack.status); |
| return 0; |
| } |
| return 1; |
| } |
|
|
| int ds4_tp_send_stop(ds4_tp *tp) { |
| return tp_send_frame(tp->control_fd, DS4_TP_FRAME_STOP, NULL, 0); |
| } |
|
|
| void ds4_tp_command_free(ds4_tp_command *command) { |
| if (!command) return; |
| free(command->tokens); |
| free(command->items); |
| memset(command, 0, sizeof(*command)); |
| command->type = DS4_TP_FRAME_ERROR; |
| } |
|
|
| static int tp_command_decode_tokens(ds4_tp_command *command, |
| const uint8_t *payload, |
| uint32_t bytes, |
| char *err, size_t errlen) { |
| if (bytes < sizeof(ds4_tp_token_command_header)) return 0; |
| ds4_tp_token_command_header h; |
| memcpy(&h, payload, sizeof(h)); |
| const uint64_t want = sizeof(h) + (uint64_t)h.count * sizeof(int32_t); |
| if (want != bytes) return 0; |
| int *tokens = malloc(h.count ? (size_t)h.count * sizeof(*tokens) : 1u); |
| if (!tokens) { |
| tp_set_err(err, errlen, "tp: command token allocation failed"); |
| return -1; |
| } |
| const int32_t *wire_tokens = (const int32_t *)(payload + sizeof(h)); |
| for (uint32_t i = 0; i < h.count; i++) tokens[i] = wire_tokens[i]; |
| command->session_id = h.session_id; |
| command->tokens = tokens; |
| command->n_tokens = h.count; |
| return 1; |
| } |
|
|
| int ds4_tp_recv_command(ds4_tp *tp, ds4_tp_command *command, |
| char *err, size_t errlen) { |
| memset(command, 0, sizeof(*command)); |
| command->type = DS4_TP_FRAME_ERROR; |
| uint32_t ftype = 0, bytes = 0; |
| if (!tp_read_frame_header(tp->control_fd, &ftype, &bytes)) { |
| tp_set_err(err, errlen, "tp: control channel closed"); |
| return 0; |
| } |
| uint8_t *payload = NULL; |
| if (bytes != 0) { |
| payload = malloc(bytes); |
| if (!payload || !tp_read_full(tp->control_fd, payload, bytes)) { |
| free(payload); |
| tp_set_err(err, errlen, "tp: truncated command frame"); |
| return 0; |
| } |
| } |
| int ok = 1; |
| switch (ftype) { |
| case DS4_TP_FRAME_SYNC: |
| case DS4_TP_FRAME_VERIFY: |
| ok = tp_command_decode_tokens(command, payload, bytes, err, errlen); |
| break; |
| case DS4_TP_FRAME_SESSION_CREATE: |
| case DS4_TP_FRAME_REWIND: { |
| ds4_tp_value_command msg; |
| if (bytes != sizeof(msg)) { ok = 0; break; } |
| memcpy(&msg, payload, sizeof(msg)); |
| command->session_id = msg.session_id; |
| command->value = msg.value; |
| break; |
| } |
| case DS4_TP_FRAME_SESSION_DESTROY: |
| case DS4_TP_FRAME_INVALIDATE: |
| if (bytes != sizeof(command->session_id)) { ok = 0; break; } |
| memcpy(&command->session_id, payload, sizeof(command->session_id)); |
| break; |
| case DS4_TP_FRAME_EVAL: { |
| ds4_tp_eval_command msg; |
| if (bytes != sizeof(msg)) { ok = 0; break; } |
| memcpy(&msg, payload, sizeof(msg)); |
| command->session_id = msg.session_id; |
| command->seq = msg.seq; |
| command->value = msg.token; |
| break; |
| } |
| case DS4_TP_FRAME_EVAL_BATCH: { |
| ds4_tp_batch_command_header h; |
| if (bytes < sizeof(h)) { ok = 0; break; } |
| memcpy(&h, payload, sizeof(h)); |
| const uint64_t want = sizeof(h) + |
| (uint64_t)h.count * sizeof(ds4_tp_batch_item); |
| if (h.count == 0 || want != bytes) { ok = 0; break; } |
| command->items = malloc((size_t)h.count * sizeof(*command->items)); |
| if (!command->items) { ok = -1; break; } |
| memcpy(command->items, payload + sizeof(h), |
| (size_t)h.count * sizeof(*command->items)); |
| command->n_items = h.count; |
| break; |
| } |
| case DS4_TP_FRAME_MIXED_BATCH: { |
| ds4_tp_mixed_command_header h; |
| if (bytes < sizeof(h)) { ok = 0; break; } |
| memcpy(&h, payload, sizeof(h)); |
| const uint64_t token_bytes = |
| (uint64_t)h.prompt_count * sizeof(int32_t); |
| const uint64_t item_bytes = |
| (uint64_t)h.item_count * sizeof(ds4_tp_batch_item); |
| const uint64_t want = sizeof(h) + token_bytes + item_bytes; |
| if (h.prompt_count == 0 || h.item_count == 0 || want != bytes) { |
| ok = 0; |
| break; |
| } |
| command->tokens = malloc((size_t)h.prompt_count * |
| sizeof(*command->tokens)); |
| command->items = malloc((size_t)h.item_count * |
| sizeof(*command->items)); |
| if (!command->tokens || !command->items) { ok = -1; break; } |
| const int32_t *wire_tokens = |
| (const int32_t *)(payload + sizeof(h)); |
| for (uint32_t i = 0; i < h.prompt_count; i++) { |
| command->tokens[i] = wire_tokens[i]; |
| } |
| memcpy(command->items, payload + sizeof(h) + token_bytes, |
| (size_t)item_bytes); |
| command->session_id = h.prefill_session_id; |
| command->n_tokens = h.prompt_count; |
| command->n_items = h.item_count; |
| break; |
| } |
| case DS4_TP_FRAME_STOP: |
| if (bytes != 0) ok = 0; |
| break; |
| default: |
| ok = 0; |
| break; |
| } |
| free(payload); |
| if (ok <= 0) { |
| ds4_tp_command_free(command); |
| if (ok == 0) { |
| tp_set_err(err, errlen, "tp: invalid command frame type %u (%u bytes)", |
| ftype, bytes); |
| } else if (!err || !err[0]) { |
| tp_set_err(err, errlen, "tp: command allocation failed"); |
| } |
| return 0; |
| } |
| command->type = (ds4_tp_frame_type)ftype; |
| return 1; |
| } |
|
|
| int ds4_tp_send_logits_half(ds4_tp *tp, const float *half, uint32_t count) { |
| return tp_send_frame(tp->control_fd, DS4_TP_FRAME_LOGITS, |
| half, count * sizeof(float)); |
| } |
|
|
| int ds4_tp_recv_logits_half(ds4_tp *tp, float *half, uint32_t count) { |
| uint32_t type = 0, bytes = 0; |
| if (!tp_read_frame_header(tp->control_fd, &type, &bytes) || |
| type != DS4_TP_FRAME_LOGITS || bytes != count * sizeof(float)) { |
| fprintf(stderr, "ds4-tp: bad logits frame (type %u bytes %u)\n", type, bytes); |
| return 0; |
| } |
| return tp_read_full(tp->control_fd, half, bytes); |
| } |
|
|
| int ds4_tp_send_verify(ds4_tp *tp, uint64_t session_id, |
| const int *drafts, uint32_t n) { |
| return tp_send_token_command(tp, DS4_TP_FRAME_VERIFY, session_id, |
| drafts, n); |
| } |
|
|
| int ds4_tp_send_verify_commit(ds4_tp *tp, int32_t full_accept, int32_t replay_n) { |
| struct { int32_t full; int32_t replay; } msg = { full_accept, replay_n }; |
| return tp_send_frame(tp->control_fd, DS4_TP_FRAME_VERIFY_COMMIT, |
| &msg, sizeof(msg)); |
| } |
|
|
| int ds4_tp_recv_verify_commit(ds4_tp *tp, int32_t *full_accept, int32_t *replay_n) { |
| uint32_t type = 0, bytes = 0; |
| struct { int32_t full; int32_t replay; } msg; |
| if (!tp_read_frame_header(tp->control_fd, &type, &bytes) || |
| type != DS4_TP_FRAME_VERIFY_COMMIT || bytes != sizeof(msg) || |
| !tp_read_full(tp->control_fd, &msg, sizeof(msg))) { |
| fprintf(stderr, "ds4-tp: bad verify-commit frame (type %u bytes %u)\n", |
| type, bytes); |
| return 0; |
| } |
| *full_accept = msg.full; |
| *replay_n = msg.replay; |
| return 1; |
| } |
|
|
| int ds4_tp_hash_check(ds4_tp *tp, uint64_t seq, uint64_t hash, char *err, size_t errlen) { |
| struct { uint64_t seq; uint64_t hash; } mine = { seq, hash }, theirs; |
| if (!tp_send_frame(tp->control_fd, DS4_TP_FRAME_HASH, &mine, sizeof(mine))) { |
| tp_set_err(err, errlen, "tp: hash send failed"); |
| return 0; |
| } |
| uint32_t type = 0, bytes = 0; |
| if (!tp_read_frame_header(tp->control_fd, &type, &bytes) || |
| type != DS4_TP_FRAME_HASH || bytes != sizeof(theirs) || |
| !tp_read_full(tp->control_fd, &theirs, sizeof(theirs))) { |
| tp_set_err(err, errlen, "tp: hash recv failed"); |
| return 0; |
| } |
| if (theirs.seq != seq || theirs.hash != hash) { |
| tp_set_err(err, errlen, |
| "tp: LOCKSTEP DIVERGENCE at seq %llu: local %016llx peer %016llx", |
| (unsigned long long)seq, |
| (unsigned long long)hash, (unsigned long long)theirs.hash); |
| return -1; |
| } |
| return 1; |
| } |
|
|
| |
| |
| |
|
|
| typedef struct { |
| uint64_t id; |
| ds4_session *session; |
| } ds4_tp_worker_session; |
|
|
| typedef struct { |
| ds4_tp_worker_session *v; |
| uint32_t len; |
| uint32_t cap; |
| } ds4_tp_worker_sessions; |
|
|
| static int tp_worker_session_index(const ds4_tp_worker_sessions *sessions, |
| uint64_t id) { |
| if (!sessions || id == 0) return -1; |
| for (uint32_t i = 0; i < sessions->len; i++) { |
| if (sessions->v[i].id == id) return (int)i; |
| } |
| return -1; |
| } |
|
|
| static ds4_session *tp_worker_session_find( |
| const ds4_tp_worker_sessions *sessions, uint64_t id) { |
| const int index = tp_worker_session_index(sessions, id); |
| return index >= 0 ? sessions->v[index].session : NULL; |
| } |
|
|
| static int tp_worker_session_add(ds4_tp_worker_sessions *sessions, |
| uint64_t id, ds4_session *session) { |
| if (!sessions || !session || id == 0 || |
| tp_worker_session_index(sessions, id) >= 0) return 0; |
| if (sessions->len == sessions->cap) { |
| uint32_t cap = sessions->cap ? sessions->cap * 2u : 8u; |
| ds4_tp_worker_session *v = |
| realloc(sessions->v, (size_t)cap * sizeof(*v)); |
| if (!v) return 0; |
| sessions->v = v; |
| sessions->cap = cap; |
| } |
| sessions->v[sessions->len++] = (ds4_tp_worker_session){ id, session }; |
| return 1; |
| } |
|
|
| static void tp_worker_session_remove(ds4_tp_worker_sessions *sessions, |
| uint32_t index) { |
| if (!sessions || index >= sessions->len) return; |
| ds4_session_free(sessions->v[index].session); |
| if (index + 1u < sessions->len) { |
| memmove(&sessions->v[index], &sessions->v[index + 1u], |
| (size_t)(sessions->len - index - 1u) * sizeof(sessions->v[0])); |
| } |
| sessions->len--; |
| } |
|
|
| static int tp_worker_send_logits(ds4_tp *tp, ds4_session *session, |
| float *logits, int vocab) { |
| if (!logits || vocab <= 0 || (vocab & 1) != 0) return 0; |
| const uint32_t vhalf = (uint32_t)vocab / 2u; |
| return ds4_session_copy_logits(session, logits, vocab) == vocab && |
| ds4_tp_send_logits_half(tp, logits + vhalf, vhalf); |
| } |
|
|
| int ds4_tp_worker_run(ds4_engine *engine, const ds4_tp_options *opt) { |
| char err[256] = ""; |
| ds4_tp_identity id = { |
| .gguf_bytes = ds4_engine_model_bytes(engine), |
| .model_id = (uint32_t)ds4_engine_model_id(engine), |
| .n_layer = (uint32_t)ds4_engine_layer_count(engine), |
| .n_embd = (uint32_t)ds4_engine_embd_dim(engine), |
| .n_vocab = (uint32_t)ds4_engine_vocab_size(engine), |
| .quant_bits = (uint32_t)ds4_engine_routed_quant_bits(engine), |
| .ctx_size = 0, |
| }; |
| ds4_engine_tp_gate_schedule(engine, |
| &id.gate_slot_start, |
| &id.gate_slot_step, |
| &id.gates_per_token); |
|
|
| ds4_tp *tp = NULL; |
| if (!ds4_tp_create(&tp, opt, &id, err, sizeof(err))) { |
| ds4_log(stderr, DS4_LOG_ERROR, "tp worker: %s", err); |
| return 1; |
| } |
| if (!ds4_engine_tp_bind(engine, tp, err, sizeof(err))) { |
| ds4_log(stderr, DS4_LOG_ERROR, "tp worker: %s", err); |
| ds4_tp_free(tp); |
| return 1; |
| } |
| ds4_tp_worker_sessions sessions = {0}; |
| const int vocab = ds4_engine_vocab_size(engine); |
| float *logits = ds4_engine_tp_vocab_split(engine) ? |
| malloc((size_t)vocab * sizeof(*logits)) : NULL; |
| if (ds4_engine_tp_vocab_split(engine) && !logits) { |
| ds4_log(stderr, DS4_LOG_ERROR, "tp worker: logits buffer allocation failed"); |
| ds4_tp_free(tp); |
| return 1; |
| } |
| ds4_log(stderr, DS4_LOG_OK, "tp worker ready for mirrored sessions"); |
|
|
| int rc = 0; |
| ds4_tokens prompt = {0}; |
| while (1) { |
| ds4_tp_command command; |
| if (!ds4_tp_recv_command(tp, &command, err, sizeof(err))) { |
| ds4_log(stderr, DS4_LOG_ERROR, "tp worker: %s", err); |
| rc = 1; |
| break; |
| } |
| if (command.type == DS4_TP_FRAME_STOP) { |
| ds4_log(stderr, DS4_LOG_DEFAULT, "tp worker: leader finished"); |
| ds4_tp_command_free(&command); |
| break; |
| } |
|
|
| if (command.type == DS4_TP_FRAME_SESSION_CREATE) { |
| ds4_session *session = NULL; |
| int status = 1; |
| if (command.session_id != 0 && command.value > 0 && |
| tp_worker_session_index(&sessions, command.session_id) < 0 && |
| ds4_session_create(&session, engine, command.value) == 0 && |
| tp_worker_session_add(&sessions, command.session_id, session)) { |
| |
| |
| ds4_session_gpu_warmup(session); |
| status = 0; |
| } else if (session) { |
| ds4_session_free(session); |
| } |
| if (!ds4_tp_send_command_ack(tp, command.session_id, status)) { |
| rc = 1; |
| } |
| ds4_tp_command_free(&command); |
| if (rc != 0) break; |
| continue; |
| } |
|
|
| if (command.type == DS4_TP_FRAME_SESSION_DESTROY) { |
| const int index = tp_worker_session_index(&sessions, |
| command.session_id); |
| const int status = index >= 0 ? 0 : 1; |
| if (index >= 0) tp_worker_session_remove(&sessions, (uint32_t)index); |
| if (!ds4_tp_send_command_ack(tp, command.session_id, status)) rc = 1; |
| ds4_tp_command_free(&command); |
| if (rc != 0) break; |
| continue; |
| } |
|
|
| ds4_session *session = |
| tp_worker_session_find(&sessions, command.session_id); |
| if (command.type != DS4_TP_FRAME_EVAL_BATCH && |
| command.type != DS4_TP_FRAME_MIXED_BATCH && !session) { |
| ds4_log(stderr, DS4_LOG_ERROR, |
| "tp worker: unknown session %llu for frame %d", |
| (unsigned long long)command.session_id, |
| (int)command.type); |
| ds4_tp_command_free(&command); |
| rc = 1; |
| break; |
| } |
|
|
| if (command.type == DS4_TP_FRAME_SYNC) { |
| prompt.len = 0; |
| for (uint32_t i = 0; i < command.n_tokens; i++) { |
| ds4_tokens_push(&prompt, command.tokens[i]); |
| } |
| int sync_rc = ds4_session_sync(session, &prompt, err, sizeof(err)); |
| if (!ds4_tp_send_command_ack(tp, command.session_id, sync_rc)) { |
| rc = 1; |
| } else if (sync_rc != 0) { |
| ds4_log(stderr, DS4_LOG_ERROR, "tp worker sync: %s", err); |
| rc = 1; |
| } else if (ds4_engine_tp_vocab_split(engine) && |
| !tp_worker_send_logits(tp, session, logits, vocab)) { |
| rc = 1; |
| } |
| } else if (command.type == DS4_TP_FRAME_EVAL) { |
| if (ds4_session_eval(session, command.value, err, sizeof(err)) != 0) { |
| ds4_log(stderr, DS4_LOG_ERROR, "tp worker eval: %s", err); |
| rc = 1; |
| } |
| } else if (command.type == DS4_TP_FRAME_VERIFY) { |
| int spec_rc = ds4_session_tp_spec_cycle(session, command.tokens, |
| (int)command.n_tokens, |
| err, sizeof(err)); |
| if (spec_rc != 0) { |
| ds4_log(stderr, DS4_LOG_ERROR, "tp worker verify: %s", err); |
| rc = 1; |
| } |
| } else if (command.type == DS4_TP_FRAME_REWIND) { |
| ds4_session_rewind(session, command.value); |
| } else if (command.type == DS4_TP_FRAME_INVALIDATE) { |
| ds4_session_invalidate(session); |
| } else if (command.type == DS4_TP_FRAME_EVAL_BATCH || |
| command.type == DS4_TP_FRAME_MIXED_BATCH) { |
| ds4_decode_item *items = |
| calloc(command.n_items, sizeof(*items)); |
| bool mapped = items != NULL; |
| for (uint32_t i = 0; mapped && i < command.n_items; i++) { |
| items[i].session = tp_worker_session_find( |
| &sessions, command.items[i].session_id); |
| items[i].token = command.items[i].token; |
| mapped = items[i].session != NULL; |
| } |
| ds4_session *prefill = NULL; |
| if (mapped && command.type == DS4_TP_FRAME_MIXED_BATCH) { |
| prefill = tp_worker_session_find(&sessions, |
| command.session_id); |
| mapped = prefill != NULL; |
| prompt.len = 0; |
| for (uint32_t i = 0; mapped && i < command.n_tokens; i++) { |
| ds4_tokens_push(&prompt, command.tokens[i]); |
| } |
| } |
| int batch_rc = 1; |
| if (mapped && command.type == DS4_TP_FRAME_EVAL_BATCH) { |
| batch_rc = ds4_sessions_eval_batch( |
| items, (int)command.n_items, err, sizeof(err)); |
| } else if (mapped) { |
| batch_rc = ds4_sessions_eval_batch_with_prefill( |
| items, (int)command.n_items, prefill, &prompt, |
| err, sizeof(err)); |
| } |
| if (!ds4_tp_send_command_ack(tp, command.session_id, batch_rc)) { |
| rc = 1; |
| } else if (batch_rc != 0) { |
| ds4_log(stderr, DS4_LOG_ERROR, |
| "tp worker batch: %s", err[0] ? err : "failed"); |
| rc = 1; |
| } else if (ds4_engine_tp_vocab_split(engine)) { |
| if (prefill && |
| !tp_worker_send_logits(tp, prefill, logits, vocab)) { |
| rc = 1; |
| } |
| for (uint32_t i = 0; rc == 0 && i < command.n_items; i++) { |
| if (!tp_worker_send_logits(tp, items[i].session, |
| logits, vocab)) rc = 1; |
| } |
| } |
| free(items); |
| } else { |
| ds4_log(stderr, DS4_LOG_ERROR, "tp worker: unexpected frame %d", |
| (int)command.type); |
| rc = 1; |
| } |
| ds4_tp_command_free(&command); |
| if (rc != 0) break; |
| } |
| ds4_tokens_free(&prompt); |
| while (sessions.len != 0) { |
| tp_worker_session_remove(&sessions, sessions.len - 1u); |
| } |
| free(sessions.v); |
| free(logits); |
| ds4_tp_free(tp); |
| return rc; |
| } |
|
|