diff --git a/.github/workflows/xdit-quick-start.yml b/.github/workflows/xdit-quick-start.yml
new file mode 100644
index 000000000..c11d2dd0c
--- /dev/null
+++ b/.github/workflows/xdit-quick-start.yml
@@ -0,0 +1,34 @@
+# xdit quick start guard - thin trigger over quick-start-template.yml; the engine owns the monitor loop, cache I/O and result publishing.
+name: xdit-quick-start
+
+concurrency:
+ group: ${{ github.event_name == 'schedule' && 'xdit-quick-start-schedule' || format('manual-{0}', github.run_id) }}
+ cancel-in-progress: false
+
+on:
+ schedule:
+ - cron: '45 */6 * * *'
+ workflow_dispatch:
+ pull_request:
+ branches: [main]
+ paths:
+ - 'sources/xdit/**'
+ - 'tests/xdit/**'
+
+permissions:
+ contents: read
+
+jobs:
+ xdit-quick-start:
+ uses: ./.github/workflows/quick-start-template.yml
+ with:
+ project: xdit
+ test_runner: '["linux-aarch64-a2-2"]'
+ image: swr.cn-south-1.myhuaweicloud.com/ascendhub/cann:9.1.0-910b-ubuntu22.04-py3.12
+ container_options: >-
+ --volume=/data/ci-cache/modelscope/xdit:/root/.cache/modelscope:z
+ timeout_minutes: 90
+ upstream_repo: xdit-project/xDiT
+ doc_url: 'https://raw.githubusercontent.com/Ascend/docs/{0}/sources/xdit/quick_start.md'
+ doc_path: https://github.com/Ascend/docs/blob/main/sources/xdit/quick_start.md
+ test_command: python -m unittest tests.xdit.test_quick_start_ascend -v 2>&1
diff --git a/_static/images/xdit.png b/_static/images/xdit.png
new file mode 100644
index 000000000..856ef9ebc
Binary files /dev/null and b/_static/images/xdit.png differ
diff --git a/conf.py b/conf.py
index 5bc93024e..902c063be 100644
--- a/conf.py
+++ b/conf.py
@@ -77,7 +77,8 @@
'sources/llama_cpp/quick_start.md',
'sources/whisper_cpp/quick_start.md',
'sources/llm_compressor/quick_start.md',
- 'sources/axolotl/quick_start.md']
+ 'sources/axolotl/quick_start.md',
+ 'sources/xdit/quick_start.md']
# -- Options for HTML output -------------------------------------------------
diff --git a/index.rst b/index.rst
index 9c4356f98..e2e19f3d4 100644
--- a/index.rst
+++ b/index.rst
@@ -336,6 +336,13 @@
扩散模型工具链,支持昇腾 NPU 加速图像生成。
+
+
+
+
扩散模型推理加速框架,支持昇腾 NPU 单卡/多卡加速图像生成。
+
+
+
@@ -463,6 +470,7 @@
:caption: 🎨 多模态、应用与评测
sources/Diffusers/index.rst
+ sources/xdit/index.rst
sources/lm_evaluation/index.rst
sources/open_clip/index.rst
sources/opencompass/index.rst
diff --git a/sources/xdit/index.rst b/sources/xdit/index.rst
new file mode 100644
index 000000000..89892f3b6
--- /dev/null
+++ b/sources/xdit/index.rst
@@ -0,0 +1,2 @@
+.. include:: quick_start.md
+ :parser: myst_parser.sphinx_
diff --git a/sources/xdit/quick_start.md b/sources/xdit/quick_start.md
new file mode 100644
index 000000000..63dc22d9a
--- /dev/null
+++ b/sources/xdit/quick_start.md
@@ -0,0 +1,162 @@
+# xDiT
+
+xDiT(PyPI 包名 `xfuser`)是一套统一的并行推理框架。本示例在单卡昇腾 NPU 上生成第一张图。
+
+## 前置条件
+
+### 硬件
+
+Atlas 900 A2 训练服务器(Ascend 910B),并按需完成物理机或容器内的设备挂载。单卡生成示例需 1 张卡,序列并行示例需 2 张卡。
+
+### 基础软件
+
+在运行本文档示例之前,你的机器上需要已经装好并可用:
+
+- 可用的 Python 环境
+- 可用的 CANN(参考[快速安装昇腾环境](https://ascend.github.io/docs/sources/ascend/quick_install.html))
+
+本文档示例在 Python 3.12、CANN 9.1.0 环境下验证通过。
+
+## 加载 CANN 环境
+
+```shell
+source /usr/local/Ascend/ascend-toolkit/set_env.sh
+```
+
+## 安装 PyTorch NPU 栈
+
+参考的版本配套如下(更多组合见 [CANN 与 PyTorch 配套表](https://github.com/Ascend/pytorch/blob/master/COMPATIBILITY.md)):
+
+| CANN | PyTorch | `torch_npu` 安装包 |
+| --- | --- | --- |
+| 9.1.0 | 2.9.0 | 2.9.0.post6 |
+| 9.1.0 | 2.10.0 | 2.10.0.post4 |
+| 9.1.0 | 2.11.0 | 2.11.0 |
+
+本示例使用第一行的组合:
+
+```shell #test-setup id="xdit-install-torch"
+pip install torch==2.9.0 torch_npu==2.9.0.post6
+```
+
+## 安装 xDiT
+
+安装 `xfuser`(PyPI 包名),并打印安装版本:
+
+```shell #test id="xdit-install"
+pip install xfuser
+python -c "from importlib.metadata import version; print('xDiT version:', version('xfuser'))"
+```
+
+输出结果如下:
+
+```shell #test-result id="xdit-install" fuzzy='...' fuzzy='xxx'
+...
+xDiT version: xxx
+```
+
+其中 `xxx` 是安装的 xDiT(`xfuser`)版本号。
+
+## 运行示例:文生图
+
+安装示例使用的 Triton 和模型下载所需的 ModelScope:
+
+```shell #test-setup
+pip install triton==3.5.0 "modelscope==1.37.0"
+```
+
+用 [SD3 medium](https://modelscope.cn/models/stabilityai/stable-diffusion-3-medium-diffusers) 在单卡上生成一张 256×256 的图。模型约 28 GB。
+
+将下面的 Python 代码保存为 `sd3_npu.py`:
+
+```python
+import os
+import sys
+import time
+
+import torch
+import torch_npu
+from modelscope import snapshot_download
+from transformers import T5EncoderModel
+from xfuser import xFuserArgs, xFuserStableDiffusion3Pipeline
+from xfuser.config import FlexibleArgumentParser
+from xfuser.core.distributed import get_runtime_state, get_world_group
+
+model_path = snapshot_download('stabilityai/stable-diffusion-3-medium-diffusers')
+
+parser = FlexibleArgumentParser(description="xFuser SD3 Arguments")
+args = xFuserArgs.add_cli_args(parser).parse_args(['--model', model_path] + sys.argv[1:])
+engine_args = xFuserArgs.from_cli_args(args)
+engine_config, input_config = engine_args.create_config()
+local_rank = get_world_group().rank
+
+text_encoder_3 = T5EncoderModel.from_pretrained(
+ model_path, subfolder="text_encoder_3", dtype=torch.float16
+)
+pipe = xFuserStableDiffusion3Pipeline.from_pretrained(
+ pretrained_model_name_or_path=model_path,
+ engine_config=engine_config,
+ dtype=torch.float16,
+ text_encoder_3=text_encoder_3,
+).to(f"npu:{local_rank}")
+pipe.prepare_run(input_config)
+
+torch.npu.synchronize(device=local_rank)
+start = time.perf_counter()
+output = pipe(
+ height=input_config.height,
+ width=input_config.width,
+ prompt=input_config.prompt,
+ num_inference_steps=input_config.num_inference_steps,
+ output_type=input_config.output_type,
+ guidance_scale=input_config.guidance_scale,
+ generator=torch.Generator(device="npu").manual_seed(input_config.seed),
+)
+torch.npu.synchronize(device=local_rank)
+elapsed = time.perf_counter() - start
+
+os.makedirs("results", exist_ok=True)
+if pipe.is_dp_last_group():
+ world_size = get_world_group().world_size
+ path = f"results/sd3_npu{world_size}_ulysses{engine_args.ulysses_degree}.png"
+ output.images[0].save(path)
+ print(f"inference time: {elapsed:.2f} sec")
+ print(f"image saved to {path}")
+get_runtime_state().destroy_distributed_env()
+```
+
+用 `torchrun` 在单卡上运行:
+
+```shell #test id="xdit-sd3-smoke"
+torchrun --nproc_per_node=1 sd3_npu.py --prompt "a tiny test sketch" --height 256 --width 256 --num_inference_steps 1 --seed 42
+```
+
+输出结果如下:
+
+```shell #test-result id="xdit-sd3-smoke" fuzzy='...' fuzzy='xxx'
+...
+inference time: xxx sec
+image saved to results/sd3_npu1_ulysses1.png
+```
+
+### 多卡运行示例
+
+同一个脚本、同一个模型,加 `--ulysses_degree 2` 在 2 卡上做序列并行,attention 用 SDPA 后端:
+
+```shell #test id="xdit-sd3-2card"
+torchrun --nproc_per_node=2 sd3_npu.py --prompt "a tiny test sketch" --height 256 --width 256 --num_inference_steps 1 --seed 42 --ulysses_degree 2 --attention_backend SDPA
+```
+
+输出结果如下:
+
+```shell #test-result id="xdit-sd3-2card" fuzzy='...' fuzzy='xxx'
+...
+inference time: xxx sec
+image saved to results/sd3_npu2_ulysses2.png
+```
+
+其中 `xxx` 为实际推理耗时,单位为秒。
+
+## 更多用法
+
+更多模型与多卡并行(PipeFusion / CFG 并行 / Ring 等)见 [xDiT examples](https://github.com/xdit-project/xDiT/tree/main/examples)。
diff --git a/tests/xdit/__init__.py b/tests/xdit/__init__.py
new file mode 100644
index 000000000..3767dfb23
--- /dev/null
+++ b/tests/xdit/__init__.py
@@ -0,0 +1,13 @@
+"""Tests package marker (injects repo tests/ into sys.path)."""
+
+from __future__ import annotations
+
+import sys
+from pathlib import Path
+
+_REPO_ROOT = Path(__file__).resolve().parents[2]
+_TESTS_ROOT = _REPO_ROOT / 'tests'
+for _p in (_TESTS_ROOT, _REPO_ROOT):
+ _ps = str(_p)
+ if _ps not in sys.path:
+ sys.path.insert(0, _ps)
diff --git a/tests/xdit/test_quick_start_ascend.py b/tests/xdit/test_quick_start_ascend.py
new file mode 100644
index 000000000..8abe2d3e9
--- /dev/null
+++ b/tests/xdit/test_quick_start_ascend.py
@@ -0,0 +1,162 @@
+"""Quick-start-Ascend test: doc under test is sources/xdit/quick_start.md."""
+
+from __future__ import annotations
+
+import os
+import re
+import subprocess
+import unittest
+from pathlib import Path
+
+from doc_test.base import MarkdownDocTestBase, TestCommand
+from doc_test.model_cache import (
+ ensure_safetensors,
+ purge_modelscope_corrupt,
+ resolve_modelscope_cache,
+)
+
+
+def _is_truthy(value: str | None) -> bool:
+ if not value:
+ return False
+ return value.strip().lower() == "true"
+
+
+def _e2e_enabled() -> bool:
+ return _is_truthy(os.environ.get("NPU_READY"))
+
+
+def _write_example_script(document: str) -> None:
+ """Write the single reader-facing Python example into the test cwd."""
+ blocks = re.findall(r"(?ms)^```python[ \t]*\r?\n(.*?)^```[ \t]*$", document)
+ if len(blocks) != 1:
+ raise AssertionError(f"expected one unlabeled Python example, found {len(blocks)}")
+ script = blocks[0].rstrip() + "\n"
+ compile(script, "sd3_npu.py", "exec")
+ Path("sd3_npu.py").write_text(script, encoding="utf-8")
+
+
+class TestQuickStartAscend(MarkdownDocTestBase, unittest.TestCase):
+ """SD3 medium smoke on 1 card + 2-card Ulysses parallel (NPU/hccl)."""
+
+ DEFAULT_COMMAND_TIMEOUT = 1800
+ USER_AGENT = "cosdt-ci-test/quick-start"
+ ERROR_MARKERS = (
+ *MarkdownDocTestBase.ERROR_MARKERS,
+ )
+
+ _CUDA_CONSTRAINTS = (
+ "cuda-toolkit<0", "cuda-python<0", "cuda-bindings<0", "cuda-core<0", "cuda-pathfinder<0",
+ "flashinfer-python<0", "nvidia-cublas<0", "nvidia-cuda-runtime<0", "nvidia-cuda-nvrtc<0",
+ "nvidia-cuda-cupti<0", "nvidia-cudnn<0", "nvidia-cudnn-frontend<0", "nvidia-cufft<0",
+ "nvidia-curand<0", "nvidia-cusolver<0", "nvidia-cusparse<0", "nvidia-cutlass-dsl<0",
+ "nvidia-cutlass-dsl-libs-base<0", "nvidia-cutlass-dsl-libs-core<0", "nvidia-cutlass-dsl-libs-cu12<0",
+ "nvidia-ml-py<0", "nvidia-nccl<0", "nvidia-nvjitlink<0", "nvidia-nvtx<0",
+ "nvidia-cublas-cu12<0", "nvidia-cuda-nvdisasm<0", "nvidia-cuda-runtime-cu12<0", "nvidia-cuda-nvrtc-cu12<0",
+ "nvidia-cuda-cupti-cu12<0", "nvidia-cudnn-cu12<0", "nvidia-cufft-cu12<0", "nvidia-curand-cu12<0",
+ "nvidia-cusolver-cu12<0", "nvidia-cusparse-cu12<0", "nvidia-cusparselt-cu12<0", "nvidia-nccl-cu12<0",
+ "nvidia-nvjitlink-cu12<0", "nvidia-nvtx-cu12<0",
+ )
+ _CONSTRAINTS_FILE = "/tmp/xdit_npu_constraints.txt"
+ _CANN_SET_ENV = "/usr/local/Ascend/ascend-toolkit/set_env.sh"
+ _PROJECT_ROOT = Path("/root/xdit-test")
+ _GENERATED_PNGS = {
+ "xdit-sd3-smoke": Path("results/sd3_npu1_ulysses1.png"),
+ "xdit-sd3-2card": Path("results/sd3_npu2_ulysses2.png"),
+ }
+
+ def _verify_generated_png(self, path: Path) -> None:
+ """Keep the PNG integrity check in CI, not in the quick start."""
+ if not path.is_file():
+ raise AssertionError(f"generated image not found: {path}")
+ image = path.read_bytes()
+ if len(image) <= 50_000:
+ raise AssertionError(
+ "generated image is suspiciously small "
+ f"({len(image)} bytes): {path}"
+ )
+ if image[:8] != b"\x89PNG\r\n\x1a\n":
+ raise AssertionError(
+ "generated image is not a PNG "
+ f"(magic={image[:8]!r}): {path}"
+ )
+ self.log(
+ f"[Step] verified generated PNG ({len(image)}B): "
+ f"{path}"
+ )
+
+ def _run_one(self, cmd, results, env, cwd, timeout, idx):
+ if isinstance(cmd, TestCommand) and cmd.id in self._GENERATED_PNGS:
+ super()._run_one(cmd, results, env, cwd, timeout, idx)
+ self._verify_generated_png(self._GENERATED_PNGS[cmd.id])
+ return
+ return super()._run_one(cmd, results, env, cwd, timeout, idx)
+
+ def pre_process(self) -> str:
+ doc = Path(__file__).resolve().parent.parent.parent / "sources" / "xdit" / "quick_start.md"
+ document = doc.read_text(encoding="utf-8")
+ _write_example_script(document)
+ return document
+
+ @classmethod
+ def prepare_environment(cls) -> None:
+ if os.path.isfile(cls._CANN_SET_ENV):
+ merged = subprocess.run(
+ ["bash", "-c", f"source {cls._CANN_SET_ENV} >/dev/null 2>&1; env"],
+ capture_output=True, text=True, check=True,
+ )
+ for line in merged.stdout.splitlines():
+ if "=" not in line:
+ continue
+ key, _, value = line.partition("=")
+ os.environ.setdefault(key, value)
+ print("setup: sourced CANN env from set_env.sh")
+ else:
+ print(f"setup: skipping CANN env source ({cls._CANN_SET_ENV} not present)")
+
+ with open(cls._CONSTRAINTS_FILE, "w", encoding="utf-8") as f:
+ f.write(chr(10).join(cls._CUDA_CONSTRAINTS) + chr(10))
+ os.environ["PIP_CONSTRAINT"] = cls._CONSTRAINTS_FILE
+
+ # purge stale xfuser from the image so the doc install block really
+ # installs the PyPI release instead of keeping a baked-in copy
+ subprocess.run(["python", "-m", "pip", "uninstall", "-y", "xfuser"],
+ capture_output=True, text=True, check=False)
+
+ # Run the documented script and write results outside the repository.
+ os.makedirs(cls._PROJECT_ROOT, exist_ok=True)
+ os.chdir(cls._PROJECT_ROOT)
+ print(f"setup: cwd -> {cls._PROJECT_ROOT}")
+
+ ps = "import torch, torch_npu\nraise SystemExit(0 if torch.npu.is_available() else 1)\n"
+ probe = subprocess.run(["python", "-c", ps], capture_output=True, check=False)
+ if probe.returncode == 0:
+ vs = subprocess.run(["python", "-c", "import torch, torch_npu; print(torch.__version__, torch_npu.__version__)"],
+ capture_output=True, text=True, check=True)
+ print(f"setup: reusing image torch stack ({vs.stdout.strip()})")
+ else:
+ print("setup: torch probe failed, doc install-torch will install the pinned stack")
+
+ os.environ["ASCEND_RT_VISIBLE_DEVICES"] = "0,1"
+
+ ensure_safetensors()
+ try:
+ purge_modelscope_corrupt(resolve_modelscope_cache())
+ except Exception as e:
+ print(f"setup: cache purge skipped ({e})")
+
+ @classmethod
+ def setUpClass(cls) -> None:
+ if _e2e_enabled():
+ cls.prepare_environment()
+
+ @unittest.skipIf(
+ not _e2e_enabled(),
+ "end-to-end requires NPU runner; set NPU_READY=true",
+ )
+ def test_runs_doc(self) -> None:
+ self.run_template()
+
+
+if __name__ == "__main__":
+ unittest.main()