Skip to content

Commit e02784b

Browse files
committed
refactor: address DeepSeek V4 DSpark review feedback.
1 parent db1b5e5 commit e02784b

27 files changed

Lines changed: 544 additions & 180 deletions

docs/src/content/docs/en/cookbook/autoregressive_models/deepseek/deepseek_v4.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -218,8 +218,8 @@ block geometry and is out of the trained distribution; use it only after an
218218
acceptance/performance evaluation. Context parallelism (`cp_size > 1`) is not
219219
supported on this path yet.
220220

221-
xLLM supports two SAS modes. The default compatibility mode works with CANN
222-
9.0 and needs no extra option. If the installed SAS operator accepts a
221+
On NPU, xLLM supports two SAS modes. The default compatibility mode works with
222+
CANN 9.0 and needs no extra option. If the installed SAS operator accepts a
223223
non-empty `ori_sparse_indices`, set `--enable_dspark_native_sas=true` to use
224224
the complete DSpark SWA window. Older operators terminate during tiling, so
225225
xLLM cannot safely detect this capability automatically.

docs/src/content/docs/zh/cookbook/autoregressive_models/deepseek/deepseek_v4.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -215,8 +215,8 @@ DeepSeek-V4-Flash-0731 权重目录即可:
215215
`dspark_block_size=5` 训练。改用其他 gamma 会改变扩散块几何,超出训练分布,
216216
需要重新验证接受率和性能。当前路径暂不支持 `cp_size > 1`
217217

218-
xLLM 支持两种 SAS 模式。默认兼容模式适配 CANN 9.0,无需增加参数;若当前
219-
SAS 算子支持非空 `ori_sparse_indices`,可设置
218+
在 NPU 上,xLLM 支持两种 SAS 模式。默认兼容模式适配 CANN 9.0,无需增加参数;
219+
若当前 SAS 算子支持非空 `ori_sparse_indices`,可设置
220220
`--enable_dspark_native_sas=true`,使用完整的 DSpark SWA 窗口。旧版算子会在
221221
tiling 阶段直接终止进程,因此无法安全地自动探测该能力。
222222

tests/core/framework/config/config_json_test.cpp

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ limitations under the License.
2424
#include "core/common/global_flags.h"
2525
#include "core/framework/config/config_utils.h"
2626
#include "core/framework/config/execution_config.h"
27+
#include "core/framework/config/kernel_config.h"
2728
#include "core/framework/config/kv_cache_config.h"
2829
#include "core/framework/config/model_config.h"
2930
#include "core/framework/config/parallel_config.h"
@@ -51,6 +52,16 @@ inline constexpr std::string_view kMalformedConfig = R"json({
5152
"block_size":
5253
})json";
5354

55+
#if !defined(USE_NPU)
56+
TEST(KernelConfigTest, RejectsNpuOnlyDsparkNativeSas) {
57+
JsonReader json_config =
58+
config::parse_json_string(R"json({"enable_dspark_native_sas":true})json");
59+
KernelConfig kernel_config;
60+
EXPECT_DEATH(kernel_config.from_json(json_config),
61+
"enable_dspark_native_sas is only supported on NPU");
62+
}
63+
#endif
64+
5465
class ConfigJsonFileFlagGuard final {
5566
public:
5667
explicit ConfigJsonFileFlagGuard(const std::string& config_json_file)

tests/core/layers/npu_torch/deepseek_v4_indexer_tests.cpp

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,31 @@ TEST_F(DeepseekV4IndexerTest, DSparkSparseTilingUsesSupportedWindow) {
203203
deepseek_v4_uses_prefill_sparse_metadata(params.meta.batch_forward_type));
204204
}
205205

206+
TEST_F(DeepseekV4IndexerTest, DSparkNativeSwaIndicesAreSharedByQueryRows) {
207+
const torch::Tensor block_table =
208+
torch::tensor({{10, 11, 12}}, torch::kInt32);
209+
const torch::Tensor query_cu_seq_lens = torch::tensor({0, 3}, torch::kInt32);
210+
const torch::Tensor seq_lens = torch::tensor({6}, torch::kInt32);
211+
212+
const torch::Tensor indices =
213+
build_dspark_swa_indices(block_table,
214+
query_cu_seq_lens,
215+
seq_lens,
216+
/*window_size=*/4,
217+
/*num_speculative_tokens=*/3,
218+
/*cache_block_size=*/4);
219+
220+
ASSERT_EQ(indices.dim(), 3);
221+
ASSERT_EQ(indices.size(0), 3);
222+
ASSERT_EQ(indices.size(1), 1);
223+
ASSERT_EQ(indices.size(2), 128);
224+
const torch::Tensor expected_prefix =
225+
torch::tensor({40, 41, 42, 43, 44, 45, -1}, torch::kInt32);
226+
for (int64_t row = 0; row < indices.size(0); ++row) {
227+
EXPECT_TRUE(torch::equal(indices[row][0].slice(0, 0, 7), expected_prefix));
228+
}
229+
}
230+
206231
TEST_F(DeepseekV4IndexerTest, DsaDummyAttentionUsesPositionDevice) {
207232
ModelInputParams params;
208233
params.meta.batch_forward_type = BatchForwardType::DECODE;

tests/core/runtime/acl_graph_executor_test.cpp

Lines changed: 84 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ limitations under the License.
2020
#include <torch_npu/torch_npu.h>
2121

2222
#include <cstdlib>
23+
#include <filesystem>
24+
#include <fstream>
2325
#include <memory>
2426
#include <optional>
2527
#include <vector>
@@ -30,6 +32,7 @@ limitations under the License.
3032
#include "core/framework/block/block.h"
3133
#include "core/framework/block/block_manager_impl.h"
3234
#include "core/framework/config/execution_config.h"
35+
#include "core/framework/config/kernel_config.h"
3336
#include "core/framework/config/speculative_config.h"
3437
#include "core/framework/kv_cache/kv_cache.h"
3538
#include "core/framework/kv_cache/kv_cache_utils.h"
@@ -47,6 +50,7 @@ limitations under the License.
4750
#include "core/runtime/acl_graph_executor_impl.h"
4851
#include "core/runtime/acl_graph_persistent_param.h"
4952
#include "core/runtime/base_executor_impl.h"
53+
#include "core/runtime/block_diffusion_model_config.h"
5054
#include "core/runtime/dflash_worker_impl.h"
5155
#include "core/runtime/mtp_async_state.h"
5256
#include "core/runtime/options.h"
@@ -1211,6 +1215,77 @@ TEST(DSparkWorkerOptionsTest, PreservesDraftBlockSize) {
12111215
EXPECT_EQ(dflash_detail::draft_model_num_speculative_tokens(options), 0);
12121216
}
12131217

1218+
TEST(BlockDiffusionConfigTest, MapsCheckpointLayersToNpuCapturePoints) {
1219+
EXPECT_EQ(block_diffusion::map_target_layer_ids_to_capture_points(
1220+
std::vector<int32_t>{0, 40, 42}),
1221+
(std::vector<int32_t>{1, 41, 43}));
1222+
}
1223+
1224+
TEST(BlockDiffusionConfigTest, PreservesDeepseekV4NpuDraftArguments) {
1225+
const std::filesystem::path config_dir =
1226+
std::filesystem::path(::testing::TempDir()) /
1227+
"xllm_block_diffusion_config_test";
1228+
std::filesystem::create_directories(config_dir);
1229+
{
1230+
std::ofstream config_file(config_dir / "config.json");
1231+
config_file << R"json({"dspark_target_layer_ids":[40,41,42]})json";
1232+
}
1233+
1234+
runtime::Options target_options;
1235+
target_options.speculative_algorithm("DSpark")
1236+
.draft_model_path(config_dir.string())
1237+
.num_speculative_tokens(5)
1238+
.is_draft_engine(false);
1239+
ModelArgs target_args;
1240+
target_args.model_type("deepseek_v4")
1241+
.n_layers(43)
1242+
.dspark_num_layers(3)
1243+
.dspark_block_size(0)
1244+
.compress_ratios({1, 1, 4});
1245+
block_diffusion::configure_model_args(
1246+
target_args, target_options, /*model_weights_path=*/"unused");
1247+
EXPECT_EQ(target_args.model_type(), "deepseek_v4");
1248+
EXPECT_EQ(target_args.n_layers(), 43);
1249+
EXPECT_EQ(target_args.dspark_block_size(), 0);
1250+
EXPECT_EQ(target_args.layers_to_capture(),
1251+
(std::vector<int32_t>{41, 42, 43}));
1252+
EXPECT_EQ(target_args.compress_ratios(), (std::vector<int32_t>{1, 1, 4}));
1253+
1254+
runtime::Options draft_options;
1255+
draft_options.speculative_algorithm("DSpark")
1256+
.num_speculative_tokens(5)
1257+
.is_draft_engine(true);
1258+
ModelArgs draft_args;
1259+
draft_args.model_type("deepseek_v4")
1260+
.n_layers(43)
1261+
.n_hash_layers(2)
1262+
.dspark_num_layers(3)
1263+
.dspark_block_size(0)
1264+
.compress_ratios({1, 1, 4});
1265+
1266+
KernelConfig& kernel_config = KernelConfig::get_instance();
1267+
const bool original_native_sas = kernel_config.enable_dspark_native_sas();
1268+
kernel_config.enable_dspark_native_sas(false);
1269+
block_diffusion::configure_model_args(
1270+
draft_args, draft_options, config_dir.string());
1271+
kernel_config.enable_dspark_native_sas(original_native_sas);
1272+
1273+
EXPECT_EQ(draft_args.model_type(), "deepseek_v4_dspark");
1274+
EXPECT_EQ(draft_args.n_layers(), 3);
1275+
EXPECT_EQ(draft_args.n_hash_layers(), 0);
1276+
EXPECT_EQ(draft_args.dspark_block_size(), 5);
1277+
EXPECT_FALSE(draft_args.dspark_use_native_sas());
1278+
EXPECT_EQ(draft_args.layers_to_capture(), (std::vector<int32_t>{41, 42, 43}));
1279+
EXPECT_EQ(draft_args.compress_ratios(), (std::vector<int32_t>{1, 1, 1}));
1280+
1281+
std::filesystem::remove_all(config_dir);
1282+
}
1283+
1284+
TEST(DSparkNativeSasConfigTest, DefaultsToCompatibilityMode) {
1285+
KernelConfig config;
1286+
EXPECT_FALSE(config.enable_dspark_native_sas());
1287+
}
1288+
12141289
TEST(DSparkWorkerInputTest, InvalidatesTargetAttentionMetadataOnly) {
12151290
ModelInputParams params;
12161291
params.attn_metadata = std::make_shared<layer::AttentionMetadata>();
@@ -1234,16 +1309,17 @@ TEST(DSparkWorkerWeightsTest, PreservesDeepseekDraftHeadAndEmbedding) {
12341309
TEST(DSparkSasFallbackTest, ChoosesCompatibleRowsUnlessNativeIsEnabled) {
12351310
ModelArgs draft_args;
12361311
draft_args.model_type("deepseek_v4_dspark");
1237-
EXPECT_TRUE(dflash_detail::uses_dsa_block_parallel_query_rows(
1238-
draft_args, /*sample_from_anchor=*/true));
1239-
EXPECT_FALSE(dflash_detail::uses_native_dspark_sas(
1240-
draft_args, /*sample_from_anchor=*/true));
1312+
EXPECT_EQ(dflash_detail::classify_dspark_sas_mode(
1313+
draft_args, /*sample_from_anchor=*/true),
1314+
dflash_detail::DSparkSasMode::COMPATIBILITY);
12411315

12421316
draft_args.dspark_use_native_sas(true);
1243-
EXPECT_FALSE(dflash_detail::uses_dsa_block_parallel_query_rows(
1244-
draft_args, /*sample_from_anchor=*/true));
1245-
EXPECT_TRUE(dflash_detail::uses_native_dspark_sas(
1246-
draft_args, /*sample_from_anchor=*/true));
1317+
EXPECT_EQ(dflash_detail::classify_dspark_sas_mode(
1318+
draft_args, /*sample_from_anchor=*/true),
1319+
dflash_detail::DSparkSasMode::NATIVE);
1320+
EXPECT_EQ(dflash_detail::classify_dspark_sas_mode(
1321+
draft_args, /*sample_from_anchor=*/false),
1322+
dflash_detail::DSparkSasMode::NOT_DSPARK);
12471323
}
12481324

12491325
TEST(DSparkWorkerWeightsTest, DedicatedVocabularyOverridesFallbackInAnyOrder) {

tests/models/model_registry_test.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ Licensed under the Apache License, Version 2.0 (the "License");
44
you may not use this file except in compliance with the License.
55
You may obtain a copy of the License at
66
7-
https://www.apache.org/licenses/LICENSE-2.0
7+
https://github.com/jd-opensource/xllm/blob/main/LICENSE
88
99
Unless required by applicable law or agreed to in writing, software
1010
distributed under the License is distributed on an "AS IS" BASIS,

xllm/core/common/global_flags.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -82,8 +82,6 @@ DECLARE_bool(enable_opt_validate_probs);
8282

8383
DECLARE_bool(enable_mtp_draft_body_tp1);
8484

85-
DECLARE_bool(enable_dspark_native_sas);
86-
8785
DECLARE_int32(speculative_suffix_cache_max_depth);
8886

8987
DECLARE_double(speculative_suffix_max_spec_factor);
@@ -403,6 +401,8 @@ DECLARE_bool(enable_aclnn_matmul);
403401

404402
DECLARE_bool(enable_aclnn_swiglu);
405403

404+
DECLARE_bool(enable_dspark_native_sas);
405+
406406
DECLARE_bool(enable_flashcomm1);
407407

408408
DECLARE_int32(flashcomm1_min_prefill_tokens);

xllm/core/distributed_runtime/speculative_output_metrics.h

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ Licensed under the Apache License, Version 2.0 (the "License");
44
you may not use this file except in compliance with the License.
55
You may obtain a copy of the License at
66
7-
https://www.apache.org/licenses/LICENSE-2.0
7+
https://github.com/jd-opensource/xllm/blob/main/LICENSE
88
99
Unless required by applicable law or agreed to in writing, software
1010
distributed under the License is distributed on an "AS IS" BASIS,
@@ -43,13 +43,11 @@ SpeculativeOutputStats calculate_speculative_output_stats_typed(
4343
for (int64_t column = 0; column < token_width; ++column) {
4444
if (data[row * token_width + column] >= static_cast<T>(0)) {
4545
++stats.committed_tokens;
46-
}
47-
}
48-
for (int64_t position = 0; position < num_speculative_tokens; ++position) {
49-
// Column 0 is always the first committed token. Draft position p was
50-
// accepted exactly when output column p+1 is non-negative.
51-
if (data[row * token_width + position + 1] >= static_cast<T>(0)) {
52-
++stats.accepted_per_position[static_cast<size_t>(position)];
46+
// Column 0 is always the first committed token. Draft position p was
47+
// accepted exactly when output column p+1 is non-negative.
48+
if (column > 0 && column <= num_speculative_tokens) {
49+
++stats.accepted_per_position[static_cast<size_t>(column - 1)];
50+
}
5351
}
5452
}
5553
}

xllm/core/distributed_runtime/worker_service.cpp

Lines changed: 32 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,10 @@ int32_t get_num_decode_seqs_for_schedule_overlap(const ForwardInput& input) {
6161
unpacked_input.sampling_params.sample_idxes.size(0));
6262
}
6363

64-
void record_speculative_metrics_from_output(const torch::Tensor& next_tokens,
65-
const runtime::Options& options) {
64+
void record_speculative_metrics_from_output(
65+
const torch::Tensor& next_tokens,
66+
const runtime::Options& options,
67+
const std::vector<std::string>& position_labels) {
6668
if (!options.enable_speculative_decode() || !next_tokens.defined() ||
6769
next_tokens.dim() != 2 || next_tokens.numel() == 0) {
6870
return;
@@ -75,6 +77,7 @@ void record_speculative_metrics_from_output(const torch::Tensor& next_tokens,
7577
token_width != num_speculative_tokens + 1) {
7678
return;
7779
}
80+
CHECK_EQ(position_labels.size(), static_cast<size_t>(num_speculative_tokens));
7881

7982
torch::Tensor tokens = next_tokens.contiguous();
8083
worker_service_detail::SpeculativeOutputStats stats =
@@ -93,7 +96,7 @@ void record_speculative_metrics_from_output(const torch::Tensor& next_tokens,
9396
stats.accepted_per_position[static_cast<size_t>(position)];
9497
num_accepted_tokens += accepted;
9598
MULTI_COUNTER_ADD(speculative_num_accepted_tokens_per_pos,
96-
std::to_string(position),
99+
position_labels[static_cast<size_t>(position)],
97100
accepted);
98101
}
99102
COUNTER_ADD(speculative_num_drafts_total, batch_size);
@@ -109,6 +112,21 @@ void record_speculative_metrics_from_output(const torch::Tensor& next_tokens,
109112
}
110113
}
111114

115+
std::vector<std::string> build_speculative_position_labels(
116+
const runtime::Options& options) {
117+
const int32_t num_speculative_tokens = options.num_speculative_tokens();
118+
if (num_speculative_tokens <= 0) {
119+
return {};
120+
}
121+
122+
std::vector<std::string> labels;
123+
labels.reserve(static_cast<size_t>(num_speculative_tokens));
124+
for (int32_t position = 0; position < num_speculative_tokens; ++position) {
125+
labels.emplace_back(std::to_string(position));
126+
}
127+
return labels;
128+
}
129+
112130
torch::Tensor clone_cpu_tensor_view(const torch::Tensor& tensor) {
113131
if (!tensor.defined()) {
114132
return tensor;
@@ -128,7 +146,10 @@ void stabilize_schedule_overlap_host_views(ForwardInput& input) {
128146

129147
WorkerService::WorkerService(runtime::Options options,
130148
const torch::Device& device)
131-
: options_(options), device_(device), initialized_(false) {
149+
: options_(options),
150+
speculative_position_labels_(build_speculative_position_labels(options)),
151+
initialized_(false),
152+
device_(device) {
132153
device_.set_device();
133154
device_.init_device_context();
134155
stream_ = device_.get_stream_from_pool();
@@ -143,9 +164,10 @@ WorkerService::WorkerService(runtime::Options options,
143164
const torch::Device& device,
144165
std::unique_ptr<Worker> worker)
145166
: options_(options),
167+
speculative_position_labels_(build_speculative_position_labels(options)),
168+
initialized_(true),
146169
device_(device),
147-
worker_(std::move(worker)),
148-
initialized_(true) {
170+
worker_(std::move(worker)) {
149171
device_.set_device();
150172
device_.init_device_context();
151173
stream_ = device_.get_stream_from_pool();
@@ -281,7 +303,8 @@ void WorkerService::step(ForwardInput& fwd_input,
281303
} else {
282304
stream_->synchronize();
283305
}
284-
record_speculative_metrics_from_output(next_tokens, options_);
306+
record_speculative_metrics_from_output(
307+
next_tokens, options_, speculative_position_labels_);
285308
}
286309
}
287310
} else {
@@ -911,7 +934,8 @@ void WorkerService::GetLastStepResult(
911934
device_.index());
912935
#endif
913936
}
914-
record_speculative_metrics_from_output(next_tokens, options_);
937+
record_speculative_metrics_from_output(
938+
next_tokens, options_, speculative_position_labels_);
915939

916940
if (next_tokens.defined() || !dit_images.empty() ||
917941
!dit_text_output.empty() ||

xllm/core/distributed_runtime/worker_service.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ limitations under the License.
1616
#pragma once
1717

1818
#include <string>
19+
#include <vector>
1920

2021
#include "runtime/forward_shared_memory_manager.h"
2122
#include "runtime/worker.h"
@@ -170,6 +171,7 @@ class WorkerService : public proto::DistributeWorker {
170171
private:
171172
// runtime options
172173
runtime::Options options_;
174+
std::vector<std::string> speculative_position_labels_;
173175

174176
bool initialized_;
175177

0 commit comments

Comments
 (0)