Enable DeepSeek V4.1 DSpark over Metal and CUDA TP
This commit is contained in:
1 parent
792e7e6aad
commit
97079d7dcf
9 files changed
+265
-57
No files matched your search
+17
-1
@@ -1688,7 +1688,8 @@ the long sparse-boundary tests; neither substitutes for the other.
|
||||
Use `DeepSeek-V4.1-Flash-DSpark.gguf`, not a V4.0 drafter. The native support
|
||||
model is 8.04 GiB and remains resident while the target can use SSD streaming.
|
||||
On 128 GiB hosts, leave room for both models and context buffers. This path
|
||||
supports one Metal, CUDA or ROCm host; TP plus DSpark must fail explicitly.
|
||||
supports one Metal, CUDA or ROCm host and two-rank Metal/CUDA TP. For TP,
|
||||
pass the matching `--dspark --mtp-model DSPARK` options on both peers.
|
||||
|
||||
- Run `python3 tests/test_deepseek41_dspark_conversion.py` and audit every
|
||||
real tensor with `python3 gguf-tools/deepseek41_dspark.py --hf SOURCE
|
||||
@@ -1709,6 +1710,12 @@ supports one Metal, CUDA or ROCm host; TP plus DSpark must fail explicitly.
|
||||
after verification. Exercise opportunistic and exact sampling. Loading an
|
||||
older target snapshot must warm missing draft features rather than reuse
|
||||
stale ones. Repeat the backend primitives, including 128-expert/top-3 routing.
|
||||
- Repeat the runtime test on physical Metal RDMA and CUDA RoCE pairs:
|
||||
`tests/test_deepseek41_dspark TARGET DSPARK --runtime-tp HOST PORT DEVICE GID`.
|
||||
Start the normal `ds4` TP worker with context 2048 and the matching drafter.
|
||||
Require all prefix commits, correction rewinds, both sampling modes and
|
||||
cancellation to finish without a lost peer. Also reject mismatched DSpark
|
||||
settings at startup (`DS4_TEST_TP_MODE_MISMATCH=3` in `test_tp_link`).
|
||||
- Build `gguf-tools/quality-testing/score_official_verify`. Compare the matching
|
||||
official manifest with `--mtp-model DSPARK` against the same command plus
|
||||
`--verify-batch 6`. Keep `--ssd-streaming --ssd-streaming-cache-experts 40gb`
|
||||
@@ -1717,6 +1724,9 @@ supports one Metal, CUDA or ROCm host; TP plus DSpark must fail explicitly.
|
||||
34816; also repeat its `manifest-long.tsv` with `--continued-prefill 32`.
|
||||
Report paired teacher-forced NLL and API top-token agreement. Greedy sampled
|
||||
prefix length and scalar-versus-verifier text equality are not quality gates.
|
||||
Repeat the scalar/verifier comparison on both physical TP pairs using the
|
||||
normal coordinator flags instead of SSD streaming; start a matching worker
|
||||
for each run. Verify the complete vocabulary, not just the local half.
|
||||
- Run real CLI and agent code/prose requests at temperature zero and above
|
||||
zero, including `--mtp-exact-sampling`. Against an otherwise idle speculative
|
||||
server, run `tests/test_server_speculative_stop.py --model deepseek-v4.1-flash
|
||||
@@ -1725,6 +1735,12 @@ supports one Metal, CUDA or ROCm host; TP plus DSpark must fail explicitly.
|
||||
coding client's edit/build/test loop and cancellation near context capacity.
|
||||
Record acceptance and end-to-end timing; working speculation does not by
|
||||
itself establish a speedup.
|
||||
- On ROCm, repeat at least ten actual server cold starts outside a debugger,
|
||||
alternating default and exact sampling. Send a request, check the answer,
|
||||
and shut down cleanly each time. Watch usable host memory, excluding CMA.
|
||||
If startup crashes, retain the binary and core or backtrace plus kernel
|
||||
logs; distinguish a userspace HIP failure from OOM or a GPU reset. A clean
|
||||
rerun is evidence against recurrence, not an explanation of a prior crash.
|
||||
|
||||
### Metal Reference Checks
|
||||
|
||||
|
||||
@@ -65,14 +65,17 @@ See [conversion](../gguf-tools/README.md#convert-v41-dspark) to build it.
|
||||
|
||||
The same options work with `ds4-agent` and `ds4-server`. Metal, CUDA and ROCm
|
||||
support single-host execution, with either resident target weights or SSD
|
||||
streaming. The drafter stays resident, so leave another 8 GiB for it. Network
|
||||
TP with V4.1 DSpark is not implemented; ordinary V4.1 TP is unchanged.
|
||||
streaming. The drafter stays resident, so leave another 8 GiB for it.
|
||||
Two-host tensor parallelism works on Metal and CUDA: add the same `--dspark
|
||||
--mtp-model FILE` options to both the coordinator and worker. Each peer keeps
|
||||
the drafter resident; the target and its verification run across both GPUs.
|
||||
|
||||
Up to five drafts are checked together. The sampling choices below apply;
|
||||
`--mtp-exact-sampling` uses sampled proposals and the full acceptance/correction
|
||||
rule for V4.1. Restored sessions warm the draft window with 128 ordinary tokens
|
||||
before speculation resumes. Measure speed on your prompts: acceptance and SSD
|
||||
cache size both matter.
|
||||
cache size both matter. V4.1 DSpark can still be slower than ordinary decoding,
|
||||
including in TP mode.
|
||||
|
||||
## GLM: built-in MTP
|
||||
|
||||
|
||||
@@ -71877,13 +71877,12 @@ static int ds4_engine_open_internal(ds4_engine **out,
|
||||
#endif
|
||||
opt->distributed.role == DS4_DISTRIBUTED_NONE &&
|
||||
!load_slice && !opt->glm_mtp &&
|
||||
(!(opt->dspark || (opt->mtp_path && opt->mtp_path[0])) || opt->tp.role == DS4_TP_NONE) &&
|
||||
!opt->first_token_test && !opt->metal_graph_test &&
|
||||
(!opt->directional_steering_file || !opt->directional_steering_file[0]) &&
|
||||
e->power_percent == 100 && opt->context_size <= 1048576;
|
||||
if (!supported) {
|
||||
fprintf(stderr, "ds4: V4.1 requires Metal, single-GPU CUDA or ROCm (network TP on Metal/CUDA); "
|
||||
"DSpark currently requires one rank; steering and legacy diagnostics "
|
||||
"steering and legacy diagnostics "
|
||||
"are not supported (maximum context 1048576)\n");
|
||||
ds4_engine_close(e);
|
||||
*out = NULL;
|
||||
@@ -73858,6 +73857,8 @@ int ds4_engine_tp_bind(ds4_engine *e, struct ds4_tp *tp, char *err, size_t errle
|
||||
}
|
||||
uint32_t execution_mode = e->cuda_tp_expert_split ?
|
||||
DS4_TP_MODE_EXPERT_INTERMEDIATE_SPLIT : 0u;
|
||||
if (DS4_MODEL_FAMILY == DS4_MODEL_FAMILY_DEEPSEEK41 && e->dspark)
|
||||
execution_mode |= DS4_TP_MODE_V41_DSPARK;
|
||||
if (DS4_MODEL_FAMILY == DS4_MODEL_FAMILY_DEEPSEEK41 &&
|
||||
!getenv("DS4_METAL_DISABLE_V41_TP_SHARED_OWNER")) {
|
||||
execution_mode |= DS4_TP_MODE_V41_SHARED_OWNER;
|
||||
@@ -78925,6 +78926,9 @@ static int ds4_session_eval_internal(ds4_session *s, int token, bool probe_mtp,
|
||||
ds4_engine *e = s->engine;
|
||||
#ifdef DS4_HAS_DEEPSEEK41_GPU
|
||||
if (ds4_session_is_ds41(s)) {
|
||||
ds41_dspark *d = s->ds41_graph.dspark;
|
||||
const bool rewindable = d && d->rewind_end == s->ds41_graph.pos &&
|
||||
d->rewind_end && d->rewind_end < d->verify_start + d->verify_count;
|
||||
if (!s->ds41_graph_ready ||
|
||||
(!s->checkpoint_valid && s->checkpoint.len != 0) ||
|
||||
s->ds41_graph.pos != (uint32_t)s->checkpoint.len ||
|
||||
@@ -78935,6 +78939,7 @@ static int ds4_session_eval_internal(ds4_session *s, int token, bool probe_mtp,
|
||||
}
|
||||
token_vec_push(&s->checkpoint, token);
|
||||
s->checkpoint_valid = true;
|
||||
if (rewindable) d->rewind_end = s->ds41_graph.pos;
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
@@ -82869,6 +82874,100 @@ static int ds4_session_eval_dspark_speculative_stochastic(
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef DS4_HAS_DEEPSEEK41_GPU
|
||||
/* The target vocabulary is sharded on CUDA. Both ranks keep the small native
|
||||
* drafter resident, then exchange its head rows before the leader applies the
|
||||
* sequential Markov bias. Metal currently keeps the complete target head. */
|
||||
static bool ds41_session_draft(ds4_session *s, int token) {
|
||||
ds4_engine *e = s->engine;
|
||||
ds41_gpu_graph *g = &s->ds41_graph;
|
||||
ds41_dspark *d = g->dspark;
|
||||
const bool split = e->tp.active && e->tp.vocab_split;
|
||||
if (!d || (split && e->tp.rank == 0 &&
|
||||
!ds4_tp_send_dspark_draft(e->tp.ctx, s->tp_session_id, token,
|
||||
(int)g->pos, DS4_TP_DSPARK_DRAFT))) return false;
|
||||
if (!ds41_dspark_forward(d, &e->model, &e->weights, &e->mtp_model, g->pos, token, g))
|
||||
return false;
|
||||
if (!split) return true;
|
||||
const uint32_t half = DS4_N_VOCAB / 2u;
|
||||
/* draft_probs is not live until Markov sampling; borrow it for the packed
|
||||
* local half rows so expansion cannot overwrite the next row. */
|
||||
if (!ds4_gpu_tensor_read(d->logits, 0, d->draft_probs,
|
||||
(uint64_t)DS41_DRAFT_ROWS * half * sizeof(float))) return false;
|
||||
for (uint32_t i = 0; i < DS41_DRAFT_ROWS; i++) {
|
||||
float *local = d->draft_probs + (uint64_t)i * half;
|
||||
float *row = d->host_logits + (uint64_t)i * DS4_N_VOCAB;
|
||||
if (e->tp.rank == 1) {
|
||||
if (!ds4_tp_send_logits_half(e->tp.ctx, local, half)) return false;
|
||||
} else {
|
||||
memcpy(row, local, half * sizeof(float));
|
||||
if (!ds4_tp_recv_logits_half(e->tp.ctx, row + half, half)) return false;
|
||||
}
|
||||
}
|
||||
return e->tp.rank == 1 || ds4_gpu_tensor_write(d->logits, 0, d->host_logits,
|
||||
(uint64_t)DS41_DRAFT_ROWS * DS4_N_VOCAB * sizeof(float));
|
||||
}
|
||||
|
||||
static bool ds41_session_verify(ds4_session *s, const int *tokens, uint32_t count) {
|
||||
ds4_engine *e = s->engine;
|
||||
ds41_gpu_graph *g = &s->ds41_graph;
|
||||
ds41_dspark *d = g->dspark;
|
||||
if (ds4_session_tp_leader(s) &&
|
||||
!ds4_tp_send_verify(e->tp.ctx, s->tp_session_id, tokens, count)) return false;
|
||||
/* Scalar TP merges its head on the host. Preserve that complete frontier
|
||||
* for a zero-prefix rewind, not the stale other half on the device. */
|
||||
if (!ds4_gpu_tensor_write(g->logits, 0, s->logits, (uint64_t)DS4_N_VOCAB * 4u) ||
|
||||
!ds41_dspark_verify(d, g, &e->model, &e->weights, tokens, count)) return false;
|
||||
if (!e->tp.active || !e->tp.vocab_split) return true;
|
||||
const uint32_t half = DS4_N_VOCAB / 2u;
|
||||
for (uint32_t i = 0; i < count; i++) {
|
||||
float *row = d->host_verified + (uint64_t)i * DS4_N_VOCAB;
|
||||
if (e->tp.rank == 1) {
|
||||
if (!ds4_tp_send_logits_half(e->tp.ctx, row + half, half)) return false;
|
||||
} else if (!ds4_tp_recv_logits_half(e->tp.ctx, row + half, half)) return false;
|
||||
}
|
||||
return e->tp.rank == 1 || ds4_gpu_tensor_write(d->verify_logits, 0, d->host_verified,
|
||||
(uint64_t)count * DS4_N_VOCAB * sizeof(float));
|
||||
}
|
||||
|
||||
static bool ds41_session_commit(ds4_session *s, const int *tokens,
|
||||
uint32_t count, uint32_t keep) {
|
||||
ds4_engine *e = s->engine;
|
||||
if (keep > count || (ds4_session_tp_leader(s) &&
|
||||
!ds4_tp_send_verify_commit(e->tp.ctx,
|
||||
keep == count ? DS4_TP_VERIFY_COMMIT_FULL : DS4_TP_VERIFY_COMMIT_PREFIX,
|
||||
(int32_t)keep))) return false;
|
||||
if (!ds41_dspark_commit(s->ds41_graph.dspark, &s->ds41_graph, keep, s->logits))
|
||||
return false;
|
||||
char err[128];
|
||||
if (ds4_session_tp_leader(s) && !ds4_tp_wait_command_ack(e->tp.ctx,
|
||||
s->tp_session_id, "V4.1 verify commit", err, sizeof(err))) return false;
|
||||
for (uint32_t i = 0; i < keep; i++) token_vec_push(&s->checkpoint, tokens[i]);
|
||||
return true;
|
||||
}
|
||||
|
||||
static int ds41_session_tp_verify(ds4_session *s, const int *tokens, uint32_t count,
|
||||
uint32_t flags, char *err, size_t errlen) {
|
||||
ds4_engine *e = s->engine;
|
||||
int32_t mode, keep;
|
||||
bool ok = !flags && s->checkpoint_valid && s->ds41_graph.dspark &&
|
||||
s->ds41_graph.pos == (uint32_t)s->checkpoint.len &&
|
||||
ds41_session_verify(s, tokens, count) &&
|
||||
ds4_tp_recv_verify_commit(e->tp.ctx, &mode, &keep);
|
||||
if (ok) ok = keep >= 0 && (uint32_t)keep <= count &&
|
||||
((mode == DS4_TP_VERIFY_COMMIT_FULL && (uint32_t)keep == count) ||
|
||||
(mode == DS4_TP_VERIFY_COMMIT_PREFIX && (uint32_t)keep < count));
|
||||
if (ok) ok = ds41_dspark_commit(s->ds41_graph.dspark, &s->ds41_graph,
|
||||
(uint32_t)keep, s->logits);
|
||||
if (ok) for (int32_t i = 0; i < keep; i++) token_vec_push(&s->checkpoint, tokens[i]);
|
||||
if (!ok) {
|
||||
s->checkpoint_valid = s->ds41_graph.valid = false;
|
||||
snprintf(err, errlen, "tp: V4.1 verification or prefix commit failed");
|
||||
}
|
||||
return ok ? 0 : 1;
|
||||
}
|
||||
#endif
|
||||
|
||||
/* TP worker side of a mirrored speculative-verify block. Runs its half of the
|
||||
* same batch verify as the leader, including per-layer combine gates, purely
|
||||
* for KV, compressor, and indexer side effects; then it obeys the commit
|
||||
@@ -82900,6 +82999,10 @@ int ds4_session_tp_spec_cycle(ds4_session *s, const int *drafts, int draft_n,
|
||||
snprintf(err, errlen, "tp: verify block beyond context");
|
||||
return 1;
|
||||
}
|
||||
#ifdef DS4_HAS_DEEPSEEK41_GPU
|
||||
if (ds4_session_is_ds41(s))
|
||||
return ds41_session_tp_verify(s, drafts, (uint32_t)draft_n, flags, err, errlen);
|
||||
#endif
|
||||
ds4_spec_frontier frontier;
|
||||
memset(&frontier, 0, sizeof(frontier));
|
||||
const int start = s->checkpoint.len;
|
||||
@@ -83088,6 +83191,17 @@ int ds4_session_tp_spec_cycle(ds4_session *s, const int *drafts, int draft_n,
|
||||
|
||||
int ds4_session_tp_dspark_draft(ds4_session *s, int token, int pos,
|
||||
uint32_t mode, char *err, size_t errlen) {
|
||||
#ifdef DS4_HAS_DEEPSEEK41_GPU
|
||||
if (s && ds4_session_is_ds41(s)) {
|
||||
ds4_engine *e = s->engine;
|
||||
if (e->tp.active && e->tp.rank == 1 && e->tp.vocab_split &&
|
||||
mode == DS4_TP_DSPARK_DRAFT && pos > 0 &&
|
||||
s->checkpoint_valid && pos == s->checkpoint.len &&
|
||||
(uint32_t)pos == s->ds41_graph.pos && ds41_session_draft(s, token)) return 0;
|
||||
snprintf(err, errlen, "tp: V4.1 draft failed or frontier differs");
|
||||
return 1;
|
||||
}
|
||||
#endif
|
||||
#if !defined(DS4_NO_GPU) && !defined(__APPLE__) && !defined(DS4_ROCM_BUILD) && \
|
||||
defined(DS4_HAS_DEEPSEEK41_GPU)
|
||||
ds4_engine *e = s ? s->engine : NULL;
|
||||
@@ -85912,7 +86026,7 @@ static int ds4_sessions_eval_batch_with_prefill_cuda(
|
||||
int ds4_test_ds41_verify_tokens(ds4_session *s, const int *tokens, int count,
|
||||
float *logits, char *err, size_t errlen) {
|
||||
if (!s || !ds4_session_is_ds41(s) || !tokens || !logits || count < 1 ||
|
||||
count > (int)DS41_VERIFY_ROWS || !s->checkpoint_valid || s->engine->tp.active ||
|
||||
count > (int)DS41_VERIFY_ROWS || !s->checkpoint_valid ||
|
||||
!s->ds41_graph.dspark || s->checkpoint.len + count > s->ctx_size) {
|
||||
snprintf(err, errlen, "invalid V4.1 verification test block");
|
||||
return 1;
|
||||
@@ -85927,14 +86041,13 @@ int ds4_test_ds41_verify_tokens(ds4_session *s, const int *tokens, int count,
|
||||
return 0;
|
||||
}
|
||||
ds41_dspark *d = s->ds41_graph.dspark;
|
||||
if (!ds41_dspark_verify(d, &s->ds41_graph, &s->engine->model, &s->engine->weights, tokens, count) ||
|
||||
!ds41_dspark_commit(d, &s->ds41_graph, count, s->logits)) {
|
||||
if (!ds41_session_verify(s, tokens, count) ||
|
||||
!ds41_session_commit(s, tokens, count, count)) {
|
||||
ds4_session_invalidate(s);
|
||||
snprintf(err, errlen, "V4.1 verification test failed");
|
||||
return 1;
|
||||
}
|
||||
memcpy(logits, d->host_verified, (uint64_t)count * DS4_N_VOCAB * sizeof(float));
|
||||
for (int i = 0; i < count; i++) token_vec_push(&s->checkpoint, tokens[i]);
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
@@ -85955,7 +86068,7 @@ static int ds41_session_speculate(ds4_session *s, int first_token, int cap, int
|
||||
if (stats) { s->dspark_stats.cycles++; s->dspark_stats.first_tokens++; }
|
||||
bool ready = d && !e->quality && !e->dspark_strict && cap > 1 &&
|
||||
first_token != eos && g->pos && g->pos <= 1048576u - DS41_DRAFT_ROWS &&
|
||||
!e->tp.active;
|
||||
ds41_tp_batch_enabled(g);
|
||||
/* Old session files contain target caches, not draft features. Warm the
|
||||
* window with ordinary decoding instead of inventing missing features. */
|
||||
if (ready) for (uint32_t pos = g->pos > 128u ? g->pos - 128u : 0; pos < g->pos; pos++)
|
||||
@@ -85969,7 +86082,7 @@ static int ds41_session_speculate(ds4_session *s, int first_token, int cap, int
|
||||
}
|
||||
const bool exact = temperature > 0 && e->dspark_exact_sampling;
|
||||
const double started = now_sec();
|
||||
if (!ds41_dspark_forward(d, &e->model, &e->weights, &e->mtp_model, g->pos, first_token))
|
||||
if (!ds41_session_draft(s, first_token))
|
||||
goto fail;
|
||||
int tokens[DS41_VERIFY_ROWS] = {first_token};
|
||||
uint32_t count = 1;
|
||||
@@ -86008,7 +86121,7 @@ static int ds41_session_speculate(ds4_session *s, int first_token, int cap, int
|
||||
s->dspark_stats.proposed_tokens += count - 1u;
|
||||
ds4_dspark_stats_note_len(s->dspark_stats.draft_len_hist, count - 1u);
|
||||
}
|
||||
if (!ds41_dspark_verify(d, g, &e->model, &e->weights, tokens, count)) goto fail;
|
||||
if (!ds41_session_verify(s, tokens, count)) goto fail;
|
||||
uint32_t keep = 1;
|
||||
int correction = -1;
|
||||
for (; keep < count; keep++) {
|
||||
@@ -86026,9 +86139,8 @@ static int ds41_session_speculate(ds4_session *s, int first_token, int cap, int
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!ds41_dspark_commit(d, g, keep, s->logits)) goto fail;
|
||||
if (!ds41_session_commit(s, tokens, count, keep)) goto fail;
|
||||
for (uint32_t i = 0; i < keep; i++) {
|
||||
token_vec_push(&s->checkpoint, tokens[i]);
|
||||
accepted[i] = tokens[i];
|
||||
}
|
||||
const uint32_t accepted_drafts = keep - 1u;
|
||||
@@ -86061,6 +86173,7 @@ fail:
|
||||
g->valid = s->checkpoint_valid = false;
|
||||
if (stats) s->dspark_stats.verifier_errors++;
|
||||
snprintf(err, errlen, "V4.1 DSpark draft/verification failed at position %u", g->pos);
|
||||
if (e->tp.active) ds4_tp_mark_failed(e->tp.ctx);
|
||||
return -1;
|
||||
}
|
||||
#endif
|
||||
@@ -87117,6 +87230,21 @@ int ds4_session_restore_speculative_prefix(ds4_session *s, int pos) {
|
||||
(void)s; (void)pos;
|
||||
return 1;
|
||||
#else
|
||||
#ifdef DS4_HAS_DEEPSEEK41_GPU
|
||||
if (s && ds4_session_is_ds41(s)) {
|
||||
ds41_gpu_graph *g = &s->ds41_graph;
|
||||
ds41_dspark *d = g->dspark;
|
||||
if (!s->checkpoint_valid || !g->valid || !d || pos < 0 ||
|
||||
d->rewind_end != (uint32_t)s->checkpoint.len ||
|
||||
(uint32_t)pos < d->verify_start || (uint32_t)pos >= d->rewind_end) return 1;
|
||||
if (!ds41_dspark_commit(d, g, (uint32_t)pos - d->verify_start, s->logits)) {
|
||||
s->checkpoint_valid = false;
|
||||
return -1;
|
||||
}
|
||||
s->checkpoint.len = pos;
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
if (!s || !s->checkpoint_valid || !s->dspark_rewind_logits ||
|
||||
s->dspark_rewind_end <= s->dspark_rewind_start ||
|
||||
s->dspark_rewind_end != s->checkpoint.len ||
|
||||
@@ -87213,11 +87341,20 @@ bool ds4_session_rewind_speculative(ds4_session *s, int pos) {
|
||||
d->rewind_end != (uint32_t)s->checkpoint.len ||
|
||||
(uint32_t)pos < d->verify_start || (uint32_t)pos >= d->rewind_end)
|
||||
return false;
|
||||
if (!ds41_dspark_commit(d, g, (uint32_t)pos - d->verify_start, s->logits)) {
|
||||
const bool mirror = ds4_session_tp_leader(s);
|
||||
ds4_engine *e = s->engine;
|
||||
if (mirror && !ds4_tp_send_spec_restore(e->tp.ctx, s->tp_session_id, pos))
|
||||
return false;
|
||||
const int local = ds4_session_restore_speculative_prefix(s, pos);
|
||||
int peer = 0;
|
||||
char err[128];
|
||||
if (mirror && !ds4_tp_wait_command_status(e->tp.ctx, s->tp_session_id,
|
||||
&peer, "V4.1 speculative restore", err, sizeof(err))) peer = -1;
|
||||
if (local || peer) {
|
||||
s->checkpoint_valid = false;
|
||||
if (mirror) ds4_session_invalidate(s);
|
||||
return false;
|
||||
}
|
||||
s->checkpoint.len = pos;
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
+17
-3
@@ -53,6 +53,7 @@ static void ds41_dspark_free(ds41_dspark *d) {
|
||||
ds41_gpu_graph *g = &d->verified[i];
|
||||
ds4_gpu_tensor_free(g->residual); ds4_gpu_tensor_free(g->pre);
|
||||
ds4_gpu_tensor_free(g->logits);
|
||||
ds4_gpu_tensor_free(g->tp_logits_half);
|
||||
for (unsigned j = 0; j < 4; j++) {
|
||||
ds4_gpu_tensor_free(g->verify_previous_kv[j]);
|
||||
ds4_gpu_tensor_free(g->verify_previous_score[j]);
|
||||
@@ -270,9 +271,11 @@ static bool ds41_dspark_stage(ds41_dspark *d, const ds4_model *m, uint32_t stage
|
||||
* GPU. Markov sampling is sequential and performed after this stage chain. */
|
||||
static DS4_MAYBE_UNUSED bool ds41_dspark_forward(ds41_dspark *d, const ds4_model *target,
|
||||
const ds4_weights *tw, const ds4_model *draft,
|
||||
uint32_t pos, int token) {
|
||||
uint32_t pos, int token, ds41_gpu_graph *target_graph) {
|
||||
if (!d || !pos || token < 0 || (uint32_t)token >= DS4_N_VOCAB) return false;
|
||||
ds41_prefill_row *b = &d->batch;
|
||||
const bool local_experts = target_graph && target_graph->tp_world == 2;
|
||||
if (local_experts) ds4_gpu_tp_suspend_expert_sharding(1);
|
||||
bool ok = ds4_gpu_begin_commands() && ds41_dspark_seed(d, draft, pos);
|
||||
if (!ok) fprintf(stderr, "ds4: V4.1 DSpark seed failed\n");
|
||||
const float pre[DS41_DRAFT_ROWS][4] = {{1, 0, 0, 0}, {1, 0, 0, 0}, {1, 0, 0, 0}, {1, 0, 0, 0}, {1, 0, 0, 0}};
|
||||
@@ -292,9 +295,12 @@ static DS4_MAYBE_UNUSED bool ds41_dspark_forward(ds41_dspark *d, const ds4_model
|
||||
if (ok) ok = ds4_gpu_hc_weighted_sum_split_tensor(b->x, b->residual,
|
||||
b->ffn_split, DS4_N_EMBD, DS4_N_HC) &&
|
||||
ds4_gpu_dsv41_quantize(b->x, DS4_N_EMBD, DS41_DRAFT_ROWS, DS4_V41_BF16) &&
|
||||
ds41_norm_batch(b->norm, b->x, draft, d->weights->stage[2].norm, DS41_DRAFT_ROWS) &&
|
||||
ds41_norm_batch(b->norm, b->x, draft, d->weights->stage[2].norm, DS41_DRAFT_ROWS);
|
||||
if (ok) ok = target_graph ?
|
||||
ds41_output_projection(target_graph, d->logits, target, tw, b->norm, DS41_DRAFT_ROWS) :
|
||||
ds41_matmul_batch(d->logits, target, tw->output, b->norm, DS41_DRAFT_ROWS, false);
|
||||
if (ds4_gpu_commands_active() && !ds4_gpu_end_commands()) ok = false;
|
||||
if (local_experts) ds4_gpu_tp_suspend_expert_sharding(0);
|
||||
if (!ok) d->kv_valid = false;
|
||||
return ok;
|
||||
}
|
||||
@@ -332,7 +338,7 @@ static DS4_MAYBE_UNUSED bool ds41_dspark_markov(ds41_dspark *d, const ds4_model
|
||||
static DS4_MAYBE_UNUSED bool ds41_dspark_verify(ds41_dspark *d, ds41_gpu_graph *g,
|
||||
const ds4_model *m, const ds4_weights *w, const int *tokens, uint32_t count) {
|
||||
if (!d || !g->valid || count < 2 || count > DS41_VERIFY_ROWS ||
|
||||
g->pos > g->ctx || count > g->ctx - g->pos || g->tp_logits_half) return false;
|
||||
g->pos > g->ctx || count > g->ctx - g->pos || !ds41_tp_batch_enabled(g)) return false;
|
||||
const uint64_t kv = DS4_N_HEAD_DIM * 4u, feature = DS4_N_EMBD * 4u;
|
||||
ds41_gpu_graph *graphs[DS41_VERIFY_ROWS];
|
||||
bool ok = ds4_gpu_begin_commands();
|
||||
@@ -352,6 +358,7 @@ static DS4_MAYBE_UNUSED bool ds41_dspark_verify(ds41_dspark *d, ds41_gpu_graph *
|
||||
}
|
||||
ds41_gpu_graph *row = &d->verified[i];
|
||||
ds4_gpu_tensor *res = row->residual, *pre = row->pre, *logits = row->logits;
|
||||
ds4_gpu_tensor *half = row->tp_logits_half;
|
||||
ds4_gpu_tensor *pool_k[4], *pool_s[4];
|
||||
memcpy(pool_k, row->verify_previous_kv, sizeof(pool_k));
|
||||
memcpy(pool_s, row->verify_previous_score, sizeof(pool_s));
|
||||
@@ -360,6 +367,13 @@ static DS4_MAYBE_UNUSED bool ds41_dspark_verify(ds41_dspark *d, ds41_gpu_graph *
|
||||
row->pre = pre ? pre : ds4_gpu_tensor_view(d->verify_pre, i * DS4_N_HC * 4u, DS4_N_HC * 4u);
|
||||
row->logits = logits ? logits : ds4_gpu_tensor_view(d->verify_logits,
|
||||
(uint64_t)i * DS4_N_VOCAB * 4u, (uint64_t)DS4_N_VOCAB * 4u);
|
||||
row->tp_logits_half = NULL;
|
||||
if (g->tp_logits_half) {
|
||||
const uint64_t bytes = (uint64_t)DS4_N_VOCAB * 2u;
|
||||
row->tp_logits_half = half ? half : ds4_gpu_tensor_view(row->logits,
|
||||
g->tp_rank * bytes, bytes);
|
||||
ok = ok && row->tp_logits_half;
|
||||
}
|
||||
row->verify_prefix = true;
|
||||
for (uint32_t owner = 0; owner < 4; owner++) {
|
||||
const uint64_t off = (i * 8u + owner * 2u) * kv;
|
||||
|
||||
@@ -2522,7 +2522,8 @@ int ds4_tp_agree_execution_mode(ds4_tp *tp, uint32_t flags, char *err, size_t er
|
||||
return 0;
|
||||
}
|
||||
const uint32_t known = DS4_TP_MODE_EXPERT_INTERMEDIATE_SPLIT |
|
||||
DS4_TP_MODE_V41_SHARED_OWNER | DS4_TP_MODE_V41_SHARED_BALANCE;
|
||||
DS4_TP_MODE_V41_SHARED_OWNER | DS4_TP_MODE_V41_SHARED_BALANCE |
|
||||
DS4_TP_MODE_V41_DSPARK;
|
||||
if (peer[0] != mine[0] || peer[1] != flags || (flags & ~known) != 0) {
|
||||
ds4_tp_mark_failed(tp);
|
||||
tp_set_err(err, errlen, "tp execution mode mismatch (peer 0x%x, local 0x%x); "
|
||||
@@ -3854,6 +3855,9 @@ int ds4_tp_worker_run(ds4_engine *engine, const ds4_tp_options *opt) {
|
||||
(int)command.n_tokens,
|
||||
(uint32_t)command.value,
|
||||
err, sizeof(err));
|
||||
if (ds4_engine_is_deepseek41(engine) &&
|
||||
!ds4_tp_send_command_ack(tp, command.session_id, spec_rc ? -1 : 0))
|
||||
spec_rc = 1;
|
||||
if (spec_rc != 0) {
|
||||
ds4_log(stderr, DS4_LOG_ERROR, "tp worker verify: %s", err);
|
||||
rc = 1;
|
||||
|
||||
@@ -112,6 +112,7 @@ enum {
|
||||
DS4_TP_MODE_EXPERT_INTERMEDIATE_SPLIT = 1u,
|
||||
DS4_TP_MODE_V41_SHARED_OWNER = 2u,
|
||||
DS4_TP_MODE_V41_SHARED_BALANCE = 4u,
|
||||
DS4_TP_MODE_V41_DSPARK = 8u,
|
||||
};
|
||||
/* Call on both engines before binding the GPU slab. Resident layouts and
|
||||
* shared-expert ownership must match before either rank can reduce results. */
|
||||
|
||||
@@ -782,18 +782,17 @@ int main(int argc, char **argv) {
|
||||
}
|
||||
}
|
||||
if (ctx_size < 1024) ctx_size = 1024;
|
||||
#ifdef DS4_TEST_HOOKS
|
||||
if (verify_batch && (!mtp_path || session_count != 1 || tp.role != DS4_TP_NONE ||
|
||||
dist.role != DS4_DISTRIBUTED_NONE))
|
||||
die("--verify-batch requires --mtp-model and one local session");
|
||||
#endif
|
||||
|
||||
char tp_err[256] = {0};
|
||||
if (!ds4_tp_adopt_distributed_options(&tp, &dist,
|
||||
tp_err, sizeof(tp_err))) {
|
||||
fprintf(stderr, "score_official: %s\n", tp_err);
|
||||
return 2;
|
||||
}
|
||||
#ifdef DS4_TEST_HOOKS
|
||||
if (verify_batch && (!mtp_path || session_count != 1 ||
|
||||
dist.role != DS4_DISTRIBUTED_NONE))
|
||||
die("--verify-batch requires --mtp-model and one session without pipeline execution");
|
||||
#endif
|
||||
if (tp.role == DS4_TP_WORKER || dist.role == DS4_DISTRIBUTED_WORKER) {
|
||||
die("score_official: start workers with ./ds4");
|
||||
}
|
||||
|
||||
@@ -73,9 +73,10 @@ done:
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int check_runtime(const char *target, const char *draft) {
|
||||
static int check_runtime(const char *target, const char *draft, const ds4_tp_options *tp_opt) {
|
||||
int rc = 1;
|
||||
ds4_engine *engine = NULL;
|
||||
ds4_tp *tp = NULL;
|
||||
ds4_session *a = NULL, *b = NULL;
|
||||
ds4_tokens prompt = {0};
|
||||
FILE *snapshot = NULL;
|
||||
@@ -88,7 +89,18 @@ static int check_runtime(const char *target, const char *draft) {
|
||||
#endif
|
||||
.context_size = 2048, .power_percent = 100, .ssd_streaming = true,
|
||||
.ssd_streaming_cache_bytes = UINT64_C(40) << 30, .dspark = true};
|
||||
if (tp_opt) { opt.tp = *tp_opt; opt.ssd_streaming = false; }
|
||||
REQUIRE(!ds4_engine_open(&engine, &opt));
|
||||
if (tp_opt) {
|
||||
ds4_tp_identity id = {.gguf_bytes = ds4_engine_model_bytes(engine),
|
||||
.model_id = ds4_engine_model_id(engine), .n_layer = DS4_N_LAYER,
|
||||
.n_embd = DS4_N_EMBD, .n_vocab = DS4_N_VOCAB,
|
||||
.quant_bits = ds4_engine_routed_quant_bits(engine), .ctx_size = 2048};
|
||||
ds4_engine_tp_gate_schedule(engine, &id.gate_slot_start, &id.gate_slot_step,
|
||||
&id.gates_per_token, id.gate_slot_mask);
|
||||
REQUIRE(ds4_tp_create(&tp, &opt.tp, &id, err, sizeof(err)));
|
||||
REQUIRE(ds4_engine_tp_bind(engine, tp, err, sizeof(err)));
|
||||
}
|
||||
REQUIRE(!ds4_session_create(&a, engine, 2048));
|
||||
REQUIRE(!ds4_session_create(&b, engine, 2048));
|
||||
for (unsigned i = 0; i < 127; i++) ds4_tokens_push(&prompt, 300 + i % 23);
|
||||
@@ -97,30 +109,32 @@ static int check_runtime(const char *target, const char *draft) {
|
||||
REQUIRE(snapshot && !ds4_session_save_payload(a, snapshot, err, sizeof(err)));
|
||||
const uint64_t bytes = ds4_session_payload_bytes(a);
|
||||
for (unsigned keep = 0; keep <= 6; keep++) {
|
||||
rewind(snapshot);
|
||||
REQUIRE(!ds4_session_load_payload(a, snapshot, bytes, err, sizeof(err)));
|
||||
rewind(snapshot);
|
||||
REQUIRE(!ds4_session_load_payload(b, snapshot, bytes, err, sizeof(err)));
|
||||
if (tp) {
|
||||
ds4_session_invalidate(a); ds4_session_invalidate(b);
|
||||
REQUIRE(!ds4_session_sync(a, &prompt, err, sizeof(err)));
|
||||
REQUIRE(!ds4_session_sync(b, &prompt, err, sizeof(err)));
|
||||
} else {
|
||||
rewind(snapshot);
|
||||
REQUIRE(!ds4_session_load_payload(a, snapshot, bytes, err, sizeof(err)));
|
||||
rewind(snapshot);
|
||||
REQUIRE(!ds4_session_load_payload(b, snapshot, bytes, err, sizeof(err)));
|
||||
}
|
||||
int ta[6], tb[6];
|
||||
for (unsigned i = 0; i < 6; i++) {
|
||||
ta[i] = 700 + i;
|
||||
tb[i] = i < keep ? ta[i] : 900 + (int)i;
|
||||
}
|
||||
ds41_dspark *da = a->ds41_graph.dspark, *db = b->ds41_graph.dspark;
|
||||
REQUIRE(ds41_dspark_verify(da, &a->ds41_graph, &engine->model, &engine->weights, ta, 6));
|
||||
REQUIRE(ds41_dspark_verify(db, &b->ds41_graph, &engine->model, &engine->weights, tb, 6));
|
||||
REQUIRE(ds41_session_verify(a, ta, 6));
|
||||
REQUIRE(ds41_session_commit(a, ta, 6, 6));
|
||||
REQUIRE(ds41_session_verify(b, tb, 6));
|
||||
REQUIRE(ds41_session_commit(b, tb, 6, keep));
|
||||
double worst = 0;
|
||||
for (uint64_t i = 0; i < (uint64_t)keep * DS4_N_VOCAB; i++)
|
||||
worst = fmax(worst, fabs(da->host_verified[i] - db->host_verified[i]));
|
||||
fprintf(stderr, "verify prefix=%u causal max=%.8g\n", keep, worst);
|
||||
REQUIRE(worst < 0.0001);
|
||||
REQUIRE(ds41_dspark_commit(da, &a->ds41_graph, 6, a->logits));
|
||||
REQUIRE(ds41_dspark_commit(db, &b->ds41_graph, keep, b->logits));
|
||||
for (unsigned i = 0; i < 6; i++) ds4_tokens_push(&a->checkpoint, ta[i]);
|
||||
if (keep < 6) REQUIRE(ds4_session_rewind_speculative(a, 127 + keep));
|
||||
for (unsigned i = 0; i < keep; i++) {
|
||||
ds4_tokens_push(&b->checkpoint, tb[i]);
|
||||
}
|
||||
REQUIRE(a->checkpoint.len == b->checkpoint.len && a->checkpoint_valid);
|
||||
REQUIRE(!memcmp(a->logits, b->logits, DS4_N_VOCAB * sizeof(float)));
|
||||
for (unsigned i = 0; i < 8; i++) {
|
||||
@@ -158,18 +172,21 @@ static int check_runtime(const char *target, const char *draft) {
|
||||
REQUIRE(a->checkpoint.len == before + count);
|
||||
}
|
||||
}
|
||||
rewind(snapshot);
|
||||
REQUIRE(!ds4_session_save_payload(a, snapshot, err, sizeof(err)));
|
||||
const uint64_t saved_bytes = ds4_session_payload_bytes(a);
|
||||
rewind(snapshot);
|
||||
REQUIRE(!ds4_session_load_payload(b, snapshot, saved_bytes, err, sizeof(err)));
|
||||
REQUIRE(!b->ds41_graph.dspark->kv_valid);
|
||||
for (unsigned i = 0; i < 3; i++) {
|
||||
REQUIRE(!ds4_session_eval(a, 1500 + i, err, sizeof(err)));
|
||||
REQUIRE(!ds4_session_eval(b, 1500 + i, err, sizeof(err)));
|
||||
REQUIRE(!memcmp(a->logits, b->logits, DS4_N_VOCAB * sizeof(float)));
|
||||
if (!tp) {
|
||||
rewind(snapshot);
|
||||
REQUIRE(!ds4_session_save_payload(a, snapshot, err, sizeof(err)));
|
||||
const uint64_t saved_bytes = ds4_session_payload_bytes(a);
|
||||
rewind(snapshot);
|
||||
REQUIRE(!ds4_session_load_payload(b, snapshot, saved_bytes, err, sizeof(err)));
|
||||
REQUIRE(!b->ds41_graph.dspark->kv_valid);
|
||||
for (unsigned i = 0; i < 3; i++) {
|
||||
REQUIRE(!ds4_session_eval(a, 1500 + i, err, sizeof(err)));
|
||||
REQUIRE(!ds4_session_eval(b, 1500 + i, err, sizeof(err)));
|
||||
REQUIRE(!memcmp(a->logits, b->logits, DS4_N_VOCAB * sizeof(float)));
|
||||
}
|
||||
}
|
||||
fprintf(stderr, "V4.1 DSpark exact=%u sampling, frontier and post-verify snapshot: PASS\n", exact);
|
||||
fprintf(stderr, "V4.1 DSpark exact=%u sampling and frontier%s: PASS\n", exact,
|
||||
tp ? " (physical TP)" : ", post-verify snapshot");
|
||||
}
|
||||
{
|
||||
int accepted[6];
|
||||
@@ -200,13 +217,27 @@ done:
|
||||
if (err[0]) fprintf(stderr, "%s\n", err);
|
||||
if (snapshot) fclose(snapshot);
|
||||
ds4_tokens_free(&prompt);
|
||||
ds4_session_free(b); ds4_session_free(a); ds4_engine_close(engine);
|
||||
ds4_session_free(b); ds4_session_free(a);
|
||||
if (tp) (void)ds4_tp_send_stop(tp);
|
||||
ds4_engine_close(engine); ds4_tp_free(tp);
|
||||
return rc;
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
if (argc == 2 && !strcmp(argv[1], "--candidate-mask")) return check_candidate_mask();
|
||||
if (argc == 4 && !strcmp(argv[3], "--runtime")) return check_runtime(argv[1], argv[2]);
|
||||
if (argc == 4 && !strcmp(argv[3], "--runtime")) return check_runtime(argv[1], argv[2], NULL);
|
||||
if (argc == 8 && !strcmp(argv[3], "--runtime-tp")) {
|
||||
char *end;
|
||||
long port = strtol(argv[5], &end, 10);
|
||||
if (*end || port < 1 || port > 65535) return 2;
|
||||
long gid = strtol(argv[7], &end, 10);
|
||||
if (*end || gid < 0 || gid > 255) return 2;
|
||||
ds4_tp_options opt = {.role = DS4_TP_LEADER, .requested = true,
|
||||
.listen_host = argv[4], .listen_port = (int)port,
|
||||
.transport = DS4_TP_TRANSPORT_RDMA, .rdma_device = argv[6],
|
||||
.rdma_gid_index = (int)gid, .rdma_gid_index_set = true};
|
||||
return check_runtime(argv[1], argv[2], &opt);
|
||||
}
|
||||
if (argc != 4) {
|
||||
fprintf(stderr, "usage: %s TARGET_GGUF DSPARK_GGUF OUTPUT_DIRECTORY\n", argv[0]);
|
||||
return 2;
|
||||
@@ -251,7 +282,7 @@ int main(int argc, char **argv) {
|
||||
REQUIRE(ds41_dspark_capture(d, 37 + stage, 0, hc, ROWS));
|
||||
REQUIRE(ds4_gpu_end_commands());
|
||||
}
|
||||
REQUIRE(ds41_dspark_forward(d, &target, &tw, &draft, ROWS, 100));
|
||||
REQUIRE(ds41_dspark_forward(d, &target, &tw, &draft, ROWS, 100, NULL));
|
||||
REQUIRE(d->kv_valid && d->kv_end == ROWS);
|
||||
REQUIRE(dump_tensor(argv[3], "main_x", d->main_x, DS41_DRAFT_WINDOW * DS4_N_EMBD));
|
||||
for (unsigned stage = 0; stage < 3; stage++) {
|
||||
@@ -263,7 +294,7 @@ int main(int argc, char **argv) {
|
||||
REQUIRE(dump_tensor(argv[3], "norm", d->batch.norm, DS41_DRAFT_ROWS * DS4_N_EMBD));
|
||||
REQUIRE(dump_tensor(argv[3], "logits", d->logits, DS41_DRAFT_ROWS * DS4_N_VOCAB));
|
||||
REQUIRE(ds4_gpu_tensor_read(d->logits, 0, d->host_logits, DS41_DRAFT_ROWS * DS4_N_VOCAB * sizeof(float)));
|
||||
REQUIRE(ds41_dspark_forward(d, &target, &tw, &draft, ROWS, 100));
|
||||
REQUIRE(ds41_dspark_forward(d, &target, &tw, &draft, ROWS, 100, NULL));
|
||||
float *second = malloc(DS41_DRAFT_ROWS * DS4_N_VOCAB * sizeof(float));
|
||||
REQUIRE(second);
|
||||
bool repeated = ds4_gpu_tensor_read(d->logits, 0, second, DS41_DRAFT_ROWS * DS4_N_VOCAB * sizeof(float)) &&
|
||||
|
||||
@@ -287,9 +287,12 @@ int main(int argc, char **argv) {
|
||||
error, sizeof(error)));
|
||||
CHECK(ds4_tp_agree_execution_mode(tp, DS4_TP_MODE_V41_SHARED_OWNER |
|
||||
DS4_TP_MODE_V41_SHARED_BALANCE, error, sizeof(error)));
|
||||
CHECK(ds4_tp_agree_execution_mode(tp, DS4_TP_MODE_V41_DSPARK |
|
||||
DS4_TP_MODE_V41_SHARED_OWNER, error, sizeof(error)));
|
||||
if (getenv("DS4_TEST_TP_MODE_MISMATCH")) {
|
||||
const uint32_t mode = atoi(getenv("DS4_TEST_TP_MODE_MISMATCH")) == 2 ?
|
||||
DS4_TP_MODE_V41_SHARED_BALANCE : DS4_TP_MODE_EXPERT_INTERMEDIATE_SPLIT;
|
||||
const int test = atoi(getenv("DS4_TEST_TP_MODE_MISMATCH"));
|
||||
const uint32_t mode = test == 3 ? DS4_TP_MODE_V41_DSPARK :
|
||||
test == 2 ? DS4_TP_MODE_V41_SHARED_BALANCE : DS4_TP_MODE_EXPERT_INTERMEDIATE_SPLIT;
|
||||
CHECK(!ds4_tp_agree_execution_mode(tp, rank ?
|
||||
mode : 0, error, sizeof(error)));
|
||||
CHECK(ds4_tp_failed(tp));
|
||||
|
||||
Reference in new issue
Block a user