Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
3521065
refactor: rename max_memory_utilization to kv_cache_memory_fraction.
zhang-minchao Jul 4, 2026
4a36e1c
feat: add embedded-python model executor for qwen3 on cuda.
zhang-minchao Jul 4, 2026
922a5fe
fix: reuse executor attn_metadata in PyCausalLM for cuda graph capture
zhang-minchao Jul 4, 2026
091be93
fix(graph): make prefill piecewise graph work across tp=1/2 and all i…
zhang-minchao Jul 5, 2026
1f57457
revert: drop kv_cache_memory_fraction, restore max_memory_utilization
zhang-minchao Jul 5, 2026
ae54b39
revert: restore flashinfer workspace default to 128MB
zhang-minchao Jul 5, 2026
99216af
refactor: restructure python model package (xllm_models -> python)
zhang-minchao Jul 5, 2026
b5b4456
docs: add multi-hardware xllm_ops op registration design
zhang-minchao Jul 5, 2026
4580864
feat: reflect PROPERTY structs so the Python model executor gets full…
zhang-minchao Jul 5, 2026
08760f5
docs: add todo for vmm piecewise graph-pool follow-up.
zhang-minchao Jul 6, 2026
1170361
style: address review feedback on the python model executor.
zhang-minchao Jul 6, 2026
bdc49ed
refactor: move weight transform logic from C++ to Python
zhang-minchao Jul 6, 2026
ec91b0d
fix: stage python model package into the correct wheel directory
zhang-minchao Jul 6, 2026
050c660
style: apply clang-format 20.1.6 to pass CI format check.
zhang-minchao Jul 6, 2026
95cd0fd
feat: decode full CUDA graph + prefill piecewise for Python model exe…
zhang-minchao Jul 7, 2026
79291ef
fix(graph): fix cpp_graph decode padding crash and optimize py_graph …
zhang-minchao Jul 7, 2026
1a971f8
refactor: remove piecewise cuda graph dead code and optimize decode p…
zhang-minchao Jul 7, 2026
6800a96
refactor: restructure ops layer into compute/attention/collectives mo…
zhang-minchao Jul 7, 2026
c83c89c
docs: update multi-hardware ops design doc to reflect ops layer restr…
zhang-minchao Jul 7, 2026
88bab7c
refactor: use flashinfer Python API directly, remove C++ attention op…
zhang-minchao Jul 8, 2026
c462e14
revert: restore Platform::is_support_sm90a() check in prefill plan
zhang-minchao Jul 9, 2026
1268520
revert: restore determine_attention_backend() in batch_prefill
zhang-minchao Jul 9, 2026
4e60716
docs: simplify multi-hardware ops design doc
zhang-minchao Jul 9, 2026
6ab9ce1
docs: merge reshape_paged_cache into compute ops row
zhang-minchao Jul 9, 2026
1296cd0
docs: remove speculative NPU landing section
zhang-minchao Jul 9, 2026
4cbf492
docs: rewrite with C++/Python interaction details
zhang-minchao Jul 9, 2026
4906483
refactor: rename meta -> attn_meta in PyCausalLM::forward
zhang-minchao Jul 9, 2026
e86582b
refactor: use full name attention_metadata_dict, no abbreviation
zhang-minchao Jul 9, 2026
2e71a16
refactor: pass model_impl via ModelContext instead of FLAGS
zhang-minchao Jul 9, 2026
e58ad90
bugfix: fix python executor tp and graph configuration.
zhang-minchao Jul 10, 2026
4ad1a40
refactor: rename cuda torch ops library files.
zhang-minchao Jul 10, 2026
5263b48
build: install flashinfer python runtime.
zhang-minchao Jul 10, 2026
e1d1a9b
docs: remove python executor design document.
zhang-minchao Jul 10, 2026
37420b3
docs: add python model execution design documents.
zhang-minchao Jul 10, 2026
25699d9
refactor: add dedicated python model executor.
zhang-minchao Jul 13, 2026
0243904
test: cover decode graph metadata updates.
zhang-minchao Jul 13, 2026
c156834
docs: simplify python executor refactor design.
zhang-minchao Jul 13, 2026
46287e8
refactor: select flashinfer plan schema from backend parameter instea…
zhang-minchao Jul 13, 2026
a70aac1
feat: pass runtime batch limit and model length to python decode grap…
zhang-minchao Jul 13, 2026
e34f936
test: cover padded decode metadata and shared buffer allocation in cu…
zhang-minchao Jul 13, 2026
219133c
docs: explain backend-not-hardware flashinfer prefill abi selection.
zhang-minchao Jul 13, 2026
6b2cb86
bugfix: fix sliding-window, linear bias, and position cast in python …
zhang-minchao Jul 13, 2026
fb25933
refactor: keep python executor changes scoped to python graph path
zhang-minchao Jul 14, 2026
c84c7f2
bugfix: release python config while holding gil.
zhang-minchao Jul 14, 2026
2c1b8f2
refactor: narrow cuda graph metadata scope
zhang-minchao Jul 14, 2026
f258610
refactor: use empty sequences for graph padding
zhang-minchao Jul 14, 2026
2d0ced8
docs: explain empty sequence graph padding
zhang-minchao Jul 14, 2026
6a9a05f
docs: add python model architecture translation
zhang-minchao Jul 14, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 23 additions & 1 deletion docker/Dockerfile.cuda
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ ARG TORCH_INDEX_URL=https://download.pytorch.org/whl/cu128
ARG TORCH_CUDA_ARCH_LIST="8.0 8.9 9.0a 10.0a 12.0a"

ARG FLASHINFER_VERSION=0.6.2
ARG FLASHINFER_PYTHON_VERSION=0.6.14
ARG FLASHINFER_CUDA_ARCH_LIST="8.0 8.9 9.0a 10.0a 12.0a"

ARG GET_PIP_URL="https://bootstrap.pypa.io/get-pip.py"
Expand Down Expand Up @@ -149,6 +150,27 @@ RUN set -eux; \
rm -rf /workspace/flashinfer

ENV FLASHINFER_OPS_PATH=/flashinfer_ops

# Install FlashInfer globally after the AOT build. Point the Python package at
# the TVM-FFI library linked by xLLM to avoid duplicate global registrations.
RUN set -eux; \
pip install --ignore-installed "packaging>=24.2"; \
pip install "flashinfer-python==${FLASHINFER_PYTHON_VERSION}"; \
site_packages="$(python -c 'import sysconfig; print(sysconfig.get_paths()["purelib"])')"; \
tvm_ffi_lib_dir="${site_packages}/tvm_ffi/lib"; \
test -f "${tvm_ffi_lib_dir}/libtvm_ffi.so"; \
test -f "${tvm_ffi_lib_dir}/libtvm_ffi_testing.so"; \
cp -f "${tvm_ffi_lib_dir}/libtvm_ffi_testing.so" \
/usr/local/lib/libtvm_ffi_testing.so; \
ln -sfn /usr/local/lib/libtvm_ffi.so \
"${tvm_ffi_lib_dir}/libtvm_ffi.so"; \
ln -sfn /usr/local/lib/libtvm_ffi_testing.so \
"${tvm_ffi_lib_dir}/libtvm_ffi_testing.so"; \
test "$(stat -Lc '%d:%i' /usr/local/lib/libtvm_ffi.so)" = \
"$(stat -Lc '%d:%i' "${tvm_ffi_lib_dir}/libtvm_ffi.so")"; \
test "$(stat -Lc '%d:%i' /usr/local/lib/libtvm_ffi_testing.so)" = \
"$(stat -Lc '%d:%i' "${tvm_ffi_lib_dir}/libtvm_ffi_testing.so")"

ENV LD_LIBRARY_PATH=/usr/local/lib:$LD_LIBRARY_PATH
ENV LD_LIBRARY_PATH=/usr/lib/x86_64-linux-gnu:$LD_LIBRARY_PATH

Expand All @@ -166,4 +188,4 @@ RUN if [[ -n "${GIT_HTTPS_MIRROR_PREFIX}" ]]; then \
uv cache clean

USER root
CMD ["/bin/bash"]
CMD ["/bin/bash"]

Large diffs are not rendered by default.

Large diffs are not rendered by default.

17 changes: 17 additions & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -461,6 +461,23 @@ def build_cmake_targets(
os.path.join(os.path.dirname(cmake_dir), "xllm/core/server/"),
)

# Stage the Python model-executor package into the wheel under the xLLM
# install tree (xllm/python/...). It is deliberately NOT declared as a
# top-level site-packages package (that would make ``import python``
# shadow globally); it is imported only when the containing xllm/ dir is
# prepended to sys.path (see --python_model_path / XLLM_PYTHON_MODEL_PATH,
# which launch_server.py defaults to the packaged xllm/ dir).
py_pkg_src = os.path.join(self.base_dir, "xllm", "python")
if os.path.isdir(py_pkg_src):
py_pkg_dst = os.path.join(extdir, "python")
if os.path.isdir(py_pkg_dst):
shutil.rmtree(py_pkg_dst)
shutil.copytree(
py_pkg_src,
py_pkg_dst,
ignore=shutil.ignore_patterns("__pycache__", "*.pyc"),
)

_stage_triton_npu_runtime_binaries(self.base_dir, extdir, self.device)

_stage_mlu_triton_kernels(self.base_dir, extdir, self.device)
Expand Down
91 changes: 88 additions & 3 deletions tests/core/framework/config/config_json_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@ limitations under the License.

#include "core/common/global_flags.h"
#include "core/framework/config/config_utils.h"
#include "core/framework/config/execution_config.h"
#include "core/framework/config/kv_cache_config.h"
#include "core/framework/config/model_config.h"
#include "core/framework/config/scheduler_config.h"

namespace xllm {
Expand All @@ -34,7 +36,10 @@ inline constexpr std::string_view kInlineConfig = R"json({
"max_memory_utilization": 0.5,
"enable_prefix_cache": false,
"max_tokens_per_batch": 8192,
"max_seqs_per_batch": 64
"max_seqs_per_batch": 64,
"model_impl": "py",
"python_model_path": "/tmp/xllm-python-model",
"python_graph_backend": "cudagraphs"
})json";

inline constexpr std::string_view kUpdatedConfig = R"json({
Expand Down Expand Up @@ -77,30 +82,80 @@ class DumpConfigJsonFlagGuard final {
std::string old_dump_config_json_file_;
};

class ConfigFlagGuard final {
public:
ConfigFlagGuard()
: old_block_size_(FLAGS_block_size),
old_max_memory_utilization_(FLAGS_max_memory_utilization),
old_enable_prefix_cache_(FLAGS_enable_prefix_cache),
old_max_tokens_per_batch_(FLAGS_max_tokens_per_batch),
old_max_seqs_per_batch_(FLAGS_max_seqs_per_batch),
old_model_impl_(FLAGS_model_impl),
old_python_model_path_(FLAGS_python_model_path),
old_python_graph_backend_(FLAGS_python_graph_backend) {}

~ConfigFlagGuard() {
FLAGS_block_size = old_block_size_;
FLAGS_max_memory_utilization = old_max_memory_utilization_;
FLAGS_enable_prefix_cache = old_enable_prefix_cache_;
FLAGS_max_tokens_per_batch = old_max_tokens_per_batch_;
FLAGS_max_seqs_per_batch = old_max_seqs_per_batch_;
FLAGS_model_impl = old_model_impl_;
FLAGS_python_model_path = old_python_model_path_;
FLAGS_python_graph_backend = old_python_graph_backend_;
}

private:
int32_t old_block_size_;
double old_max_memory_utilization_;
bool old_enable_prefix_cache_;
int32_t old_max_tokens_per_batch_;
int32_t old_max_seqs_per_batch_;
std::string old_model_impl_;
std::string old_python_model_path_;
std::string old_python_graph_backend_;
};

class StartupConfigGuard final {
public:
StartupConfigGuard()
: kv_cache_config_(KVCacheConfig::get_instance()),
: model_config_(ModelConfig::get_instance()),
execution_config_(ExecutionConfig::get_instance()),
kv_cache_config_(KVCacheConfig::get_instance()),
scheduler_config_(SchedulerConfig::get_instance()),
old_model_impl_(model_config_.model_impl()),
old_python_model_path_(model_config_.python_model_path()),
old_python_graph_backend_(execution_config_.python_graph_backend()),
old_block_size_(kv_cache_config_.block_size()),
old_enable_prefix_cache_(kv_cache_config_.enable_prefix_cache()),
old_max_tokens_per_batch_(scheduler_config_.max_tokens_per_batch()),
old_max_seqs_per_batch_(scheduler_config_.max_seqs_per_batch()),
old_enable_chunked_prefill_(
scheduler_config_.enable_chunked_prefill()) {}

~StartupConfigGuard() {
model_config_.model_impl(old_model_impl_)
.python_model_path(old_python_model_path_);
execution_config_.python_graph_backend(old_python_graph_backend_);
kv_cache_config_.block_size(old_block_size_)
.enable_prefix_cache(old_enable_prefix_cache_);
scheduler_config_.max_tokens_per_batch(old_max_tokens_per_batch_)
.max_seqs_per_batch(old_max_seqs_per_batch_)
.enable_chunked_prefill(old_enable_chunked_prefill_);
}

private:
ModelConfig& model_config_;
ExecutionConfig& execution_config_;
KVCacheConfig& kv_cache_config_;
SchedulerConfig& scheduler_config_;
std::string old_model_impl_;
std::string old_python_model_path_;
std::string old_python_graph_backend_;
int32_t old_block_size_;
bool old_enable_prefix_cache_;
int32_t old_max_tokens_per_batch_;
int32_t old_max_seqs_per_batch_;
bool old_enable_chunked_prefill_;
};

Expand Down Expand Up @@ -133,6 +188,13 @@ std::filesystem::path config_test_file_path() {

TEST(ConfigJsonTest, FromJsonUsesParsedOverrides) {
const JsonReader json = config::parse_json_string(kInlineConfig);
ConfigFlagGuard flag_guard;

ModelConfig model_config;
model_config.from_json(json);

ExecutionConfig execution_config;
execution_config.from_json(json);

KVCacheConfig kv_cache_config;
kv_cache_config.from_flags();
Expand All @@ -147,12 +209,24 @@ TEST(ConfigJsonTest, FromJsonUsesParsedOverrides) {
EXPECT_FALSE(kv_cache_config.enable_prefix_cache());
EXPECT_EQ(scheduler_config.max_tokens_per_batch(), 8192);
EXPECT_EQ(scheduler_config.max_seqs_per_batch(), 64);
// model_impl is no longer canonicalized: the raw "py" alias is preserved and
// recognized via is_python_model_impl(). from_json still mirrors it into the
// matching gflag.
EXPECT_EQ(model_config.model_impl(), "py");
EXPECT_TRUE(ModelConfig::is_python_model_impl(model_config.model_impl()));
EXPECT_EQ(model_config.python_model_path(), "/tmp/xllm-python-model");
EXPECT_EQ(execution_config.python_graph_backend(), "cudagraphs");

EXPECT_EQ(FLAGS_model_impl, "py");
EXPECT_EQ(FLAGS_python_model_path, "/tmp/xllm-python-model");
EXPECT_EQ(FLAGS_python_graph_backend, "cudagraphs");

EXPECT_EQ(kv_cache_config.kv_cache_dtype(), "auto");
EXPECT_EQ(scheduler_config.max_decode_token_per_sequence(), 256);
}

TEST(ConfigJsonTest, LoadJsonFileReadsConfigFixture) {
ConfigFlagGuard flag_guard;
const std::filesystem::path config_path = config_test_file_path();
ASSERT_TRUE(std::filesystem::exists(config_path)) << config_path;

Expand Down Expand Up @@ -194,6 +268,7 @@ TEST(ConfigJsonTest, LoadJsonFileReadsConfigFixture) {
}

TEST(ConfigJsonTest, InitializeLoadsConfigJsonFileFromFlag) {
ConfigFlagGuard config_flag_guard;
const std::filesystem::path config_path =
std::filesystem::temp_directory_path() / "xllm_config_json_test.json";
write_config_file(config_path, kInlineConfig);
Expand All @@ -213,6 +288,7 @@ TEST(ConfigJsonTest, InitializeLoadsConfigJsonFileFromFlag) {
}

TEST(ConfigJsonTest, InitializeReusesCachedConfigJsonForSameFile) {
ConfigFlagGuard config_flag_guard;
const std::filesystem::path config_path =
std::filesystem::temp_directory_path() /
"xllm_config_json_test_cached.json";
Expand Down Expand Up @@ -295,24 +371,33 @@ TEST(ConfigJsonTest, DumpStartupConfigWritesNonDefaultValuesOnly) {
DumpConfigJsonFlagGuard flag_guard(dump_path.string());
StartupConfigGuard startup_config_guard;

ModelConfig::get_instance().model_impl("python").python_model_path(
"/tmp/xllm-python-model");
ExecutionConfig::get_instance().python_graph_backend("cudagraphs");
KVCacheConfig::get_instance().block_size(256).enable_prefix_cache(false);
SchedulerConfig::get_instance()
.max_tokens_per_batch(2048)
.max_seqs_per_batch(128)
.enable_chunked_prefill(false);
FLAGS_enable_dump_config_json = true;

config::dump_startup_config();

ASSERT_TRUE(std::filesystem::exists(dump_path));
const nlohmann::ordered_json config_json = read_json_file(dump_path);
EXPECT_EQ(config_json.at("model_impl").get<std::string>(), "python");
EXPECT_EQ(config_json.at("python_model_path").get<std::string>(),
"/tmp/xllm-python-model");
EXPECT_EQ(config_json.at("python_graph_backend").get<std::string>(),
"cudagraphs");
EXPECT_EQ(config_json.at("block_size").get<int32_t>(), 256);
EXPECT_FALSE(config_json.at("enable_prefix_cache").get<bool>());
EXPECT_EQ(config_json.at("max_tokens_per_batch").get<int32_t>(), 2048);
EXPECT_EQ(config_json.at("max_seqs_per_batch").get<int32_t>(), 128);
EXPECT_FALSE(config_json.at("enable_chunked_prefill").get<bool>());

EXPECT_FALSE(config_json.contains("max_cache_size"));
EXPECT_FALSE(config_json.contains("kv_cache_dtype"));
EXPECT_FALSE(config_json.contains("max_seqs_per_batch"));
EXPECT_FALSE(config_json.contains("priority_strategy"));

std::filesystem::remove(dump_path);
Expand Down
15 changes: 15 additions & 0 deletions tests/core/kernels/cuda/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -69,3 +69,18 @@ cc_test(
glog::glog
)
target_link_libraries(fp8_quant_test PRIVATE brpc)

cc_test(
NAME
xllm_ops_test
SRCS
xllm_ops_test.cpp
DEPS
:cuda_kernels
torch
GTest::gtest_main
glog::glog
)
# Embedded-interpreter test path: needs libtorch_python (THPVariable / at::Tensor
# pybind caster) and libpython.
target_link_libraries(xllm_ops_test PRIVATE torch_python Python::Python)
Loading
Loading