feat: add embedded-python model executor for qwen3 on cuda.#1891
feat: add embedded-python model executor for qwen3 on cuda.#1891zhang-minchao wants to merge 47 commits into
Conversation
There was a problem hiding this comment.
Code Review
This pull request introduces a Python model executor framework that allows executing Python-defined model graphs, such as Qwen3, within xLLM's C++ worker using an embedded CPython interpreter. It adds the C++ bridge, a lightweight config reflection system, and the Python-side model, layers, op dispatching, and graph runner. Feedback on the changes focuses on strict adherence to the repository's style guide, including using project-root-relative paths for #include directives, preferring fixed-width integers and emplace_back in C++, using CHECK instead of TORCH_CHECK, ensuring complete Python type annotations, and routing Python diagnostic logs through the shared logger instead of print().
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
7430c11 to
479ab8a
Compare
c962bc8 to
eb22c0b
Compare
be4d1a1 to
357910e
Compare
99a0f54 to
6f8e5e6
Compare
| fi | ||
|
|
||
| # Install tvm-ffi | ||
| RUN set -eux; \ |
There was a problem hiding this comment.
remove tvm-ffi installation manually.
| # Install flashinfer + AOT compile | ||
| ENV FLASHINFER_VERSION=${FLASHINFER_VERSION} | ||
| ENV FLASHINFER_CUDA_ARCH_LIST=${FLASHINFER_CUDA_ARCH_LIST} | ||
| RUN set -eux; \ |
- cpp_framework_python_model_architecture.md: document the architecture decision to add python model execution on the c++ serving framework and why python owns both the model and its ModelExecutor. - refactor_python_model_impl.md: describe the implementable refactor plan covering cross-language interface, object lifecycle, migration order and acceptance conditions, baselined on PR xLLM-AI#1891.
06baa0a to
584611d
Compare
…s bridge - Delete xllm_attention_ops.cpp/h (batch_decode/prefill/update_plan torch ops were only used by the Python executor, now replaced by flashinfer Python API) - Move reshape_paged_cache registration into xllm_ops_library.cpp - layers/attention.py: use flashinfer BatchDecodeWithPagedKVCacheWrapper / BatchPrefillWithRaggedKVCacheWrapper directly instead of torch.ops.xllm_ops - Rename py_model_bridge → py_model_helper, consolidate PyDictVisitor and dtype_to_string utilities - Move py_causal_lm to models/llm/ (where other CausalLM implementations live) - Add core/kernels/xllm_torch_ops.h as device-agnostic dispatch header - Remove USE_CUDA guard from PyCausalLM compilation (device-agnostic) - Add FlashinferWorkspace skip for model_impl=python in llm_worker_impl.cpp
The backend == "fa3" change was unnecessary — main's hardware-based branching is correct for the ragged prefill path.
The uri-based backend detection was unnecessary — main's determine_attention_backend() call is correct.
Remove outdated references to deleted xllm_attention_ops.cpp, redundant argumentation, and premature NPU landing checklist. Keep only the current architecture table, minimal extension guide, and risk notes.
Add complete description of the three interaction phases (init config dict, weight loading, per-step forward args) and remove speculative PrivateUse1 references.
The variable holds attention metadata, not generic metadata. Rename for clarity and update the design doc to match.
Add model_impl field to ModelContext so model_registry.cpp no longer reads the global FLAGS_model_impl directly. The caller (llm_worker_impl) sets it before calling create_llm_model.
- initialize PyTorch NCCL TP collectives and honor JSON executor settings. - prevent KV padding overflow and remove obsolete graph batch configuration. - add config, dump, and embedded Python regression coverage.
Update the Python executor design document to match the current CUDA, TP, JSON, and decode graph implementation.
Install FlashInfer 0.6.14 after the AOT build and make the embedded Python runtime share the xLLM TVM-FFI libraries.
- cpp_framework_python_model_architecture.md: document the architecture decision to add python model execution on the c++ serving framework and why python owns both the model and its ModelExecutor. - refactor_python_model_impl.md: describe the implementable refactor plan covering cross-language interface, object lifecycle, migration order and acceptance conditions, baselined on PR xLLM-AI#1891.
…d of hardware probe. - batch_prefill: determine FA2/FA3 call schema from the URI suffix (_sm90) instead of re-evaluating hardware and mask at runtime. - flashinfer_planinfo: select short (16-param) vs long (19-param) plan args from the explicit backend string, removing the platform.h dependency.
…h runner. - Remove max_seqs_per_batch from config dict; pass it as an explicit constructor argument through PyExecutorImpl → ModelExecutor → runner. - DecodeCudaGraphRunner now takes max_model_len and pre-allocates a single shared paged_kv_indices buffer sized for the worst case, reused across all bucket entries.
…da graph. - Add DecodeGraphSharesMaximumBlockTableBufferAcrossBuckets to verify the single shared paged_kv_indices buffer is reused across buckets. - Add ModelExecutorUsesExplicitRuntimeBatchLimit to confirm the runtime batch limit flows through to the decode runner. - Update DecodeMetadataFastPath* tests for max_seqs_per_batch=4 and assert the full padded shape including kv_cu_seq_lens and qo_indptr. - Add MakeFa2PrefillParams helper to pin FA2 for piecewise prefill eager references, isolating graph capture correctness from FA2/FA3 numerical differences.
Clarify at both call sites (batch_prefill.cpp ragged_run and flashinfer_planinfo.cpp prefill plan) why the FlashInfer ABI is chosen from the loaded module/backend (fa2 19-arg vs fa3 16-arg) rather than the SM90 hardware probe, which silently broke fa2-forced-on-SM90 graph piecewise prefill. Comment only; no logic change.
…model executor. - flashinfer: map sliding window W to window_left W-1 to match the native attention convention, so SWA models attend the correct span. - linear: add optional bias to Column/RowParallelLinear (column bias sharded on the output dim; row bias replicated and added once after all-reduce) and load qkv/o_proj bias when attention_bias is set; standard Qwen3 (attention_bias=False) is unchanged. - qwen3: cast position ids to int64 once in Qwen3Model.forward instead of once per layer inside fused_qk_norm_rope; the single cast stays inside the captured decode graph so replay re-casts static_positions correctly.
7fe566f to
2d0ced8
Compare
Description
Add an embedded-CPython model executor that runs Qwen3 inference on CUDA through the xLLM runtime, as an opt-in alternative to the native C++ model path. The C++ engine drives an in-process Python interpreter to execute the model forward pass, while all compute stays on the existing
xllm_opsfused CUDA kernels, so the Python path is numerically validated against the native path (byte-identical greedy decode at both tp=1 and tp=2).Enable it with
--model_impl=python --python_model_path=<path-to-xllm>; the default native path is unchanged.Main pieces:
PyCausalLM+py_model_helper(pybind) let the C++ executor construct and call a Python model like any nativeCausalLM, sharing the KV cache, forward inputs, and attention metadata.xllm/python/) — Qwen3 model, layers (linear / layernorm / embedding / rotary), op dispatch, triton kernels, and a cudagraph-capablemodel_runner, all built onxllm_ops.PROPERTYstruct reflection (property_reflect.h+macros.h) — dependency-free field reflection behind an opt-inREFLECT_PROPERTIES, so the fullModelArgs/ParallelArgscross the pybind boundary without hand-maintained field lists.xllm_opsop registration — ops layer structured as compute/collectives modules; TP collectives registered astorch.library.custom_opfor Dynamo capture; attention uses flashinfer Python API directly.服务启动
Performance
Full regression on Qwen3-0.6B using two dedicated H100 GPUs (
cuda:6andcuda:7), input length 1024, output length 256, andCUDA_LAUNCH_BLOCKING=0. The run completed build, JSON-only Python cudagraphstartup, byte-identical C++/Python greedy parity at TP1 and TP2, and the same
cross-batch sweep for all four variants. The batch list deliberately mixes
bucket boundaries and odd values to exercise decode-graph padding.
Report generated on 2026-07-14:
log/regression/batch_sweep_20260714_175345.md.TP=1 complete comparison (Median TTFT / TPOT, ms)
TP=2 complete comparison (Median TTFT / TPOT, ms)
Takeaways:
+2.0%, and TP2 from -1.4% to +6.8% across all tested batches.
lower than py_eager at both TP degrees because decode replays a captured CUDA
graph instead of dispatching the model from CPython every step.
sensitive and are not used as the decode-performance acceptance criterion.
pad?=y) validate empty-sequence padding: host planner anddevice runtime metadata use repeated terminal indptr values, zero KV-length
deltas, and no dummy page allocation. All padded buckets passed at TP1 and
TP2.
Related Issues
Change Type