Skip to content

Commit e3172ec

Browse files
committed
bugfix: correct DSpark block_size sourcing and native SWA index build.
Three DeepSeek-V4 DSpark defects surfaced by the test suite: - The model loader copied dspark_block_size into the shared target args, arming non-causal DSpark attention on the target model. Leave it unset in the loader; the draft worker now defaults it from the checkpoint's block_size (overridable by --num_speculative_tokens) via read_block_size(). - build_dspark_swa_indices used true division for the block-column index, yielding a float tensor that torch::gather rejects. Use torch::floor_divide. - The WrapAroundRingBuffer test hard-coded start_pos = kv - window, dropping the q_len term; correct the expected slots to (kv - q_len) - window.
1 parent 403c641 commit e3172ec

4 files changed

Lines changed: 27 additions & 19 deletions

File tree

tests/core/layers/npu_torch/deepseek_v4_indexer_tests.cpp

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -228,9 +228,9 @@ TEST_F(DeepseekV4IndexerTest, DSparkNativeSwaIndicesAreSharedByQueryRows) {
228228

229229
TEST_F(DeepseekV4IndexerTest, DSparkNativeSwaIndicesWrapAroundRingBuffer) {
230230
// Two-block ring buffer with kv_len=10 forces the SWA window to span both
231-
// ring entries and to wrap. Positions 6..9 fall into block_column 1, and
232-
// position 8..9 wrap back to block_column 0 once the ring rotates once
233-
// (positions / block_size = {1,1,2,2}, then % ring_size(2) = {1,1,0,0}).
231+
// ring entries and to wrap. Visible positions 5..9 map through
232+
// block_column = pos/2 % ring_size(2) -> {0,1,1,0,0}, wrapping back to
233+
// block_column 0 once the ring rotates.
234234
const torch::Tensor block_table = torch::tensor({{20, 21}}, torch::kInt32);
235235
const torch::Tensor query_cu_seq_lens = torch::tensor({0, 1}, torch::kInt32);
236236
const torch::Tensor seq_lens = torch::tensor({10}, torch::kInt32);
@@ -246,11 +246,12 @@ TEST_F(DeepseekV4IndexerTest, DSparkNativeSwaIndicesWrapAroundRingBuffer) {
246246
ASSERT_EQ(indices.dim(), 3);
247247
ASSERT_EQ(indices.size(0), 1);
248248
ASSERT_EQ(indices.size(1), 1);
249-
// start_pos = max(kv-window, 0) = 6, so the visible window is positions
250-
// 6,7,8,9. block_column = pos/2 % 2 -> {1,1,0,0}; slot = block_id * 2 +
251-
// pos%2 -> {21*2+0, 21*2+1, 20*2+0, 20*2+1} = {42, 43, 40, 41}.
249+
// start_pos = max((kv - q_len) - window, 0) = (10-1)-4 = 5, so the visible
250+
// window is positions 5,6,7,8,9. block_column = pos/2 % 2 -> {0,1,1,0,0};
251+
// slot = block_id*2 + pos%2 -> {20*2+1, 21*2+0, 21*2+1, 20*2+0, 20*2+1} =
252+
// {41, 42, 43, 40, 41}.
252253
const torch::Tensor expected_prefix =
253-
torch::tensor({42, 43, 40, 41, -1}, torch::kInt32);
254+
torch::tensor({41, 42, 43, 40, 41}, torch::kInt32);
254255
EXPECT_TRUE(torch::equal(indices[0][0].slice(0, 0, 5), expected_prefix));
255256
}
256257

xllm/core/layers/npu_torch/deepseek_sparse_attention.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -529,8 +529,8 @@ torch::Tensor build_dspark_swa_indices(const torch::Tensor& block_table,
529529
index_width, torch::TensorOptions().dtype(torch::kLong).device(device));
530530
torch::Tensor valid = columns.unsqueeze(0) < visible_lens.unsqueeze(1);
531531
torch::Tensor positions = start_pos.unsqueeze(1) + columns.unsqueeze(0);
532-
torch::Tensor block_columns =
533-
(positions / cache_block_size).remainder(block_table.size(1));
532+
torch::Tensor block_columns = torch::floor_divide(positions, cache_block_size)
533+
.remainder(block_table.size(1));
534534
torch::Tensor block_ids = block_table.gather(/*dim=*/1, block_columns);
535535
torch::Tensor slot_ids =
536536
block_ids * cache_block_size + positions.remainder(cache_block_size);

xllm/core/runtime/worker_impl.cpp

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -168,24 +168,30 @@ std::vector<int32_t> read_capture_layer_ids(
168168
}
169169

170170
#if defined(USE_NPU)
171+
int32_t read_block_size(const std::string& model_weights_path) {
172+
JsonReader reader;
173+
const std::string config_path = model_weights_path + "/config.json";
174+
CHECK(reader.parse(config_path))
175+
<< "Failed to parse DSpark draft config: " << config_path;
176+
return reader.value_or<int32_t>("dspark_block_size", 0);
177+
}
178+
171179
void configure_deepseek_v4_dspark_args(ModelArgs& args,
172180
const runtime::Options& options) {
173181
CHECK_GT(args.dspark_num_layers(), 0)
174182
<< "DeepSeek-V4 DSpark requires at least one draft layer.";
175183
args.n_layers(args.dspark_num_layers());
176184
args.n_hash_layers(0);
177185

178-
// --num_speculative_tokens overrides the checkpoint's dspark_block_size.
179-
const int32_t ckpt_block_size = args.dspark_block_size();
186+
// Default to the checkpoint's block_size; --num_speculative_tokens overrides.
187+
const int32_t ckpt_block_size = read_block_size(options.model_path());
180188
const int32_t user_num_spec = options.num_speculative_tokens();
181-
if (user_num_spec > 0) {
182-
if (user_num_spec != ckpt_block_size) {
183-
LOG(WARNING) << "--num_speculative_tokens=" << user_num_spec
184-
<< " overrides DSpark checkpoint dspark_block_size="
185-
<< ckpt_block_size << ".";
186-
}
187-
args.dspark_block_size(user_num_spec);
189+
if (user_num_spec > 0 && user_num_spec != ckpt_block_size) {
190+
LOG(WARNING) << "--num_speculative_tokens=" << user_num_spec
191+
<< " overrides DSpark checkpoint dspark_block_size="
192+
<< ckpt_block_size << ".";
188193
}
194+
args.dspark_block_size(user_num_spec > 0 ? user_num_spec : ckpt_block_size);
189195

190196
// DSpark stages are all standard SWA layers. Their stage ids are not target
191197
// model layer ids, so target compress_ratios[0..N) must not be reused.

xllm/models/llm/deepseek_v4.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2088,7 +2088,8 @@ inline void load_deepseek_v4_model_args(const JsonReader& json,
20882088
json.value_or<std::vector<int32_t>>("dspark_target_layer_ids",
20892089
std::vector<int32_t>{})
20902090
.size());
2091-
LOAD_ARG_OR(dspark_block_size, "dspark_block_size", 0);
2091+
// Don't arm dspark_block_size on the shared target (enables non-causal DSpark
2092+
// attention there); the draft worker sets it from the checkpoint.
20922093

20932094
// Token ids
20942095
LOAD_ARG_OR(bos_token_id, "bos_token_id", 0);

0 commit comments

Comments
 (0)