Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
46 changes: 43 additions & 3 deletions .github/workflows/build-release.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
name: Build release
name: Build and publish release

on:
workflow_dispatch:
push:
tags: ["v*"]

Expand All @@ -16,6 +17,7 @@ jobs:
with:
python-version: "3.11"
- name: Verify tag matches package version
if: github.event_name == 'push'
env:
RELEASE_TAG: ${{ github.ref_name }}
run: |
Expand All @@ -37,9 +39,11 @@ jobs:
source "$RUNNER_TEMP/aspec-build-venv/bin/activate"
echo "$VIRTUAL_ENV/bin" >> "$GITHUB_PATH"
echo "VIRTUAL_ENV=$VIRTUAL_ENV" >> "$GITHUB_ENV"
python -m pip install --upgrade pip build
python -m pip install --upgrade pip build twine
- name: Build source and wheel distributions
run: python -m tools.build_release --outdir dist
- name: Check PyPI distribution metadata
run: python -m twine check --strict dist/*.whl dist/*.tar.gz
- name: Save audited distributions and runtime requirements
uses: actions/upload-artifact@v7
with:
Expand Down Expand Up @@ -73,10 +77,46 @@ jobs:
python -m pip install -r requirements/ci-cpu.txt
# Deliberately test the wheel and patch CLI with a CPU dependency subset.
# This is not installation/acceptance of the production GPU runtime.
python -m pip install --no-deps dist/*.whl sglang==0.5.21
python -m pip install --no-deps dist/ling_aspec_runtime-*.whl sglang==0.5.21
- name: Exercise installed commands outside the checkout
env:
CUDA_VISIBLE_DEVICES: ""
HF_HUB_OFFLINE: "1"
TRANSFORMERS_OFFLINE: "1"
run: python tools/check_wheel_install.py

publish:
needs: [build, wheel-install]
if: >-
github.repository == 'inclusionAI/ASpec' &&
github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v')
runs-on: ubuntu-latest
environment:
name: pypi
url: https://pypi.org/project/ling-aspec/
steps:
- uses: actions/download-artifact@v8
with:
name: aspec-distributions
path: dist/
- name: Verify distribution checksums
working-directory: dist
run: sha256sum --check SHA256SUMS
- name: Select audited Python distributions
run: |
mkdir pypi-dist
cp dist/*.whl dist/*.tar.gz pypi-dist/
- name: Require PyPI API token
env:
PYPI_API_TOKEN: ${{ secrets.PYPI_API_TOKEN }}
run: |
if [ -z "$PYPI_API_TOKEN" ]; then
echo "::error::Set PYPI_API_TOKEN in the repository's pypi environment secrets."
exit 1
fi
- name: Publish to PyPI
uses: pypa/gh-action-pypi-publish@7245906fb61fa33a13554f9d5fb856fe61758086 # v1.14.3
with:
password: ${{ secrets.PYPI_API_TOKEN }}
packages-dir: pypi-dist/
attestations: false
2 changes: 2 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ jobs:
tests/test_environment_scripts.py \
tests/test_onboarding.py \
tests/test_dependency_extras.py \
tests/test_target_environment.py \
tests/test_target_service.py \
tests/test_cudnn_gqa_mode.py \
tests/test_dependency_audit.py \
tests/test_draft_model_config.py \
Expand Down
13 changes: 13 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,19 @@

## Unreleased

- Install independent Draft (`ling-aspec`) and Target (`ling-aspec-target`) environments,
sharing the version-aligned `ling-aspec-runtime` code package. Target retains SGLang's own
dependency requirements; no resolver overrides are used. Start `aspec target` and `aspec train`
in their respective environments and connect by service address. Optional managed-local
supervision uses the same service interface and retains full checkpoint/resume.
- Keep `aspec patch-sglang` limited to patching files. Report role-specific worker provenance,
and expose pip CUDA toolkit libraries through a private JIT development view.
- Set the first PyPI version to `0.1.0b0`; developers maintain release versions with the `b0`
suffix while CI checks the matching tag without changing the version.
- Use `ling-aspec` as the distribution name, retaining the `aspec` namespace and CLI. Publish
audited tag artifacts to PyPI after wheel installation checks through the GitHub `pypi` environment.
- Allow repeatable manual build/install validation without publication; reserve PyPI uploads
for version tag pushes.
- Consolidate supplementary examples around distinct workflows; use recipe/config overrides for
alternative lengths, backends and structure combinations.
- Advance fully committed restored epochs for repeating training sources, including new
Expand Down
12 changes: 9 additions & 3 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,19 +21,25 @@ Use a dedicated Linux venv and the [pinned runtime](docs/user-guide/installation

```bash
bash setup.sh --dev
bash setup.sh --target --dev
source .venv/bin/activate
python -m ruff check aspec tests tools setup.py
python -m tools.check_docs
CUDA_VISIBLE_DEVICES="" HF_HUB_OFFLINE=1 TRANSFORMERS_OFFLINE=1 bash tools/run_in_env.sh python -m pytest -q
CUDA_VISIBLE_DEVICES="" HF_HUB_OFFLINE=1 TRANSFORMERS_OFFLINE=1 bash tools/run_in_env.sh python -m pytest -q --ignore=tests/test_sgl_engine_factory.py --ignore=tests/test_inference_concurrency.py
python -m tools.build_release --outdir /absolute/path/to/new-release-dist
source .venv-target/bin/activate
CUDA_VISIBLE_DEVICES="" HF_HUB_OFFLINE=1 TRANSFORMERS_OFFLINE=1 aspec doctor --target
python -m pytest -q tests/test_sgl_engine_factory.py tests/test_inference_concurrency.py tests/test_sgl_engine_decode.py
```

Default tests are fast/offline. GPU, tokenizer, training and cuDNN gates need explicit switches
declared in their test files. A skip is not GPU acceptance. Public CI checks lint, local doc links,
compilation, plus a curated CPU/offline contract suite installed from `requirements/ci-cpu.txt`
on pull requests and configured branch pushes. The separate [release build](.github/workflows/build-release.yml)
workflow builds and audits sdist/wheel artifacts and checks wheel installation only when a `v*` tag
is pushed; see [release instructions](docs/releasing.md). Neither workflow certifies GPU health or
workflow builds and audits sdist/wheel artifacts and checks wheel installation on manual runs and
`v*` tag pushes. Manual runs only validate the artifacts and their installation.
In `inclusionAI/ASpec`, tag pushes publish `ling-aspec` to PyPI through the `pypi` environment;
see [release instructions](docs/releasing.md). Neither workflow certifies GPU health or
replaces the complete pinned-runtime suite above. Equivalent remote checks are not proof that hosted
GitHub Actions has already run.

Expand Down
1 change: 1 addition & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y --no-ins
WORKDIR /opt/ASpec
COPY . .
RUN bash setup.sh --venv /opt/aspec-venv --python /usr/bin/python3.12
RUN bash setup.sh --target --venv /opt/aspec-target --python /usr/bin/python3.12
ENV VIRTUAL_ENV=/opt/aspec-venv
ENV PATH="/opt/aspec-venv/bin:${PATH}"
CMD ["bash"]
41 changes: 30 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,32 +52,51 @@ paths have explicit support boundaries, and DFlash2 remains experimental.
Scale from a first multi-GPU run to static multi-node training, with NVLink, TCP and RDMA data paths.
Check [supported paths and limitations](docs/support.md), then browse the [recipes](docs/user-guide/recipes.md) for starting points.

## Start here
## Installation

1. [Run source setup](docs/user-guide/installation.md); [containers](docs/user-guide/containers.md) are also available.
2. [Choose Qwen3-8B or Ling tiny](docs/user-guide/quickstart.md) for the same two-GPU Plan, train,
report, resume and export workflow.
3. [Choose a training recipe](docs/user-guide/recipes.md) and [prepare training data](docs/user-guide/data.md).
In an activated Linux virtual environment:

Recipes cover Qwen3-8B, Qwen3.5-9B Native MTP, Qwen3.8-27B long context, and Ling tiny/flash 3.0/flash 3.1.
For complete feature workflows, see [offline feature generation and replay](examples/qwen3-8b-offline/README.md)
and [DSA dense → indexer → sparse training](examples/qwen3-8b-dsa/README.md).
```bash
pip install ling-aspec
```

This installs the Draft training environment. In a **separate Target virtual environment**, install
the SGLang integration and apply its patch:

```bash
pip install ling-aspec-target
aspec patch-sglang
aspec target
```

Keep the Target service running, then start training in the Draft environment below.
The default connection is `http://127.0.0.1:30000`; set `inference.target_service.address`
for a remote Target. Each command uses its own environment.

See the [installation guide](docs/user-guide/installation.md) for prerequisites, source installation
and release wheels, or use the [container setup](docs/user-guide/containers.md).
Optionally run `aspec doctor` to check the environment.

From the repository root:
## Quickstart

With two NVLink-connected GPUs:

```bash
bash setup.sh
source .venv/bin/activate
CUDA_VISIBLE_DEVICES=0,1 aspec quickstart ling-tiny --output outputs/ling-tiny-001
# Or choose Qwen3-8B:
CUDA_VISIBLE_DEVICES=0,1 aspec quickstart qwen3-8b --output outputs/qwen3-8b-001
```

Both profiles use bundled raw conversations, their own tokenizer/template and an eight-step
train/resume/native-export check. Add `--target /absolute/path/to/model` to use a local checkpoint.
See [Quickstart](docs/user-guide/quickstart.md) for the full workflow, then choose a
[training recipe](docs/user-guide/recipes.md) and [prepare your data](docs/user-guide/data.md).
For training with Target-generated data and held-out evaluation, continue with the
[Qwen3-8B DSpark recipe](docs/user-guide/recipes.md#qwen3-8b-dspark-quality-recipe).

Additional workflows cover [offline feature generation and replay](examples/qwen3-8b-offline/README.md)
and [DSA dense → indexer → sparse training](examples/qwen3-8b-dsa/README.md).

## Performance

ASpec improves both Draft execution and the complete online training pipeline. The
Expand Down
35 changes: 27 additions & 8 deletions README_zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,23 +47,42 @@ Qwen、Ling 上的 DSpark/DFlash 是主要优化方向和 recipe 维护重点;
从首次多卡运行扩展到静态多机训练,支持 NVLink、TCP 和 RDMA 数据路径。
先查看[支持范围与已知限制](docs/support.md),再从 [recipe 导航](docs/user-guide/recipes.md)选择起点。

## 开始使用
## 安装

1. 按[安装指南](docs/user-guide/installation.md)一键创建源码环境,也可选择[容器](docs/user-guide/containers.md);
2. 在[快速上手](docs/user-guide/quickstart.md)中选择 Qwen3-8B 或 Ling tiny,用两张 GPU 完成
相同的 Plan、训练、报告、恢复和导出流程;
3. 根据[recipe 导航](docs/user-guide/recipes.md)选择算法/结构并准备实际数据。
在已激活的 Linux 虚拟环境中安装:

```bash
pip install ling-aspec
```

该命令安装 Draft 训练依赖。在独立的 Target 虚拟环境中安装 SGLang 集成并应用补丁:

```bash
pip install ling-aspec-target
aspec patch-sglang
aspec target
```

随后在 Draft 环境启动训练;Target 服务保持运行。默认连接 `http://127.0.0.1:30000`,远程 Target 通过
`inference.target_service.address` 配置地址。两个命令各自使用当前环境。

环境要求、源码安装和本地 wheel 安装见[安装指南](docs/user-guide/installation.md),
也可使用[容器](docs/user-guide/containers.md)。可选执行 `aspec doctor` 检查环境。

## 快速上手

使用两张 NVLink 互联的 GPU:

```bash
bash setup.sh
source .venv/bin/activate
CUDA_VISIBLE_DEVICES=0,1 aspec quickstart ling-tiny --output outputs/ling-tiny-001
# 或选择 Qwen3-8B:
CUDA_VISIBLE_DEVICES=0,1 aspec quickstart qwen3-8b --output outputs/qwen3-8b-001
```

两个入口都使用内置原始对话和各自的 tokenizer/template,完成 8 步训练、精确恢复和 native 导出加载。
使用已有模型时添加 `--target /absolute/path/to/model`。使用 Target 生成的数据开展训练与
使用已有模型时添加 `--target /absolute/path/to/model`。完整流程见[快速上手](docs/user-guide/quickstart.md),
实际训练可从 [recipe 导航](docs/user-guide/recipes.md)选择算法与结构,并[准备数据](docs/user-guide/data.md)。
使用 Target 生成的数据开展训练与
独立评估,可继续阅读 [Qwen3-8B DSpark recipe](docs/user-guide/recipes.md#qwen3-8b-dspark-quality-recipe)。

## 性能
Expand Down
45 changes: 45 additions & 0 deletions aspec/_environment.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,46 @@
# limitations under the License.

"""Prepare this venv's native libraries before executing a fresh process."""
import hashlib
import os
import sys
import sysconfig
from pathlib import Path


def _toolkit_view(toolkit, env):
"""Expose pip's CUDA layout to JITs expecting a development toolkit.

NVIDIA wheels ship lib/libcudart.so.13, while SGLang links -lcudart from
CUDA_HOME/lib64. Keep the development aliases in ASpec's private cache;
the installed wheels and host toolkit remain untouched.
"""
cache = Path(env.get("ASPEC_CACHE_HOME") or
Path(env.get("XDG_CACHE_HOME") or Path.home() / ".cache") / "aspec")
identity = hashlib.sha256(str(toolkit).encode()).hexdigest()[:16]
view = cache.expanduser().resolve() / "cuda-toolkit" / identity
libraries = view / "lib64"
libraries.mkdir(parents=True, exist_ok=True)

def link(destination, source):
try:
destination.symlink_to(source, target_is_directory=source.is_dir())
except FileExistsError:
if not destination.is_symlink() or destination.readlink() != source:
raise ValueError(f"Unexpected file in private CUDA toolkit view: {destination}")

for source in toolkit.iterdir():
if source.name != "lib64":
link(view / source.name, source)
for source in sorted((toolkit / "lib").iterdir(), key=lambda p: (len(p.name), p.name)):
link(libraries / source.name, source)
if ".so." in source.name:
alias = libraries / (source.name.split(".so.", 1)[0] + ".so")
if not alias.is_symlink():
link(alias, source)
return view


def command_environment():
if sys.platform != "linux" or sys.version_info[:2] < (3, 11):
raise ValueError("Use Linux with Python >=3.11.")
Expand All @@ -40,8 +74,19 @@ def command_environment():
raise ValueError("ASPEC_CUDA_COMPAT_DIR must contain libcuda.so.1.")
paths.append(str(path))
purelib = Path(sysconfig.get_paths()["purelib"])
toolkit = purelib / "nvidia/cu13"
if not env.get("CUDA_HOME") and not env.get("CUDA_PATH") and (toolkit / "bin/nvcc").is_file():
view = _toolkit_view(toolkit, env)
env["CUDA_HOME"] = str(view)
env["ASPEC_AUTO_CUDA_HOME"] = str(view)
env["PATH"] = str(view / "bin") + os.pathsep + env.get("PATH", "")
previous = env.get("LIBRARY_PATH", "")
env["LIBRARY_PATH"] = str(view / "lib64") + (os.pathsep + previous if previous else "")
paths.append(str(view / "lib64"))
paths.extend(str(p) for p in (purelib / "nvidia/cuda_runtime/lib", purelib / "torch/lib")
if p.is_dir())
if (toolkit / "lib").is_dir():
paths.append(str(toolkit / "lib"))
if paths:
previous = env.get("LD_LIBRARY_PATH", "")
env["LD_LIBRARY_PATH"] = ":".join(paths + ([previous] if previous else []))
Expand Down
29 changes: 29 additions & 0 deletions aspec/_target_worker.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Copyright 2026 Ant Group Co., Ltd.
# SPDX-License-Identifier: Apache-2.0
"""Bootstrap the optional managed-local Target with its own native libraries."""
import os
import sys
from pathlib import Path

from aspec._environment import execute


def main():
os.environ["VIRTUAL_ENV"] = sys.prefix
automatic_toolkit = os.environ.pop("ASPEC_AUTO_CUDA_HOME", None)
if automatic_toolkit and os.environ.get("CUDA_HOME") == automatic_toolkit:
os.environ.pop("CUDA_HOME", None)
os.environ["PATH"] = str(Path(sys.executable).parent) + os.pathsep + os.environ.get("PATH", "")
if cache := os.environ.get("ASPEC_CACHE_HOME"):
if Path(cache).name != "target":
os.environ["ASPEC_CACHE_HOME"] = str(Path(cache) / "target")
# The supervisor belongs to Draft. Never load its CUDA libraries in Target.
os.environ.pop("LD_LIBRARY_PATH", None)
os.environ.pop("LIBRARY_PATH", None)
os.environ.pop("PYTHONPATH", None)
os.environ.pop("PYTHONHOME", None)
execute([sys.executable, *sys.argv[1:]])


if __name__ == "__main__":
main()
17 changes: 17 additions & 0 deletions aspec/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,18 @@
# limitations under the License.

"""Workflow shortcuts and native-library bootstrap for the public CLI."""
import importlib.metadata
import sys
from pathlib import Path

from aspec._environment import execute


def main():
arguments = sys.argv[1:]
modules = {
"train": "aspec.train_entry",
"target": "aspec.inference.service",
"quickstart": "aspec.commands.quickstart",
"recipes": "aspec.commands.recipes",
"doctor": "aspec.commands.doctor",
Expand All @@ -45,6 +49,19 @@ def main():
module = modules[arguments.pop(0)]
else:
module = "aspec.train_entry"
marker = Path(sys.prefix) / ".aspec-role"
target_role = marker.is_file() and marker.read_text().strip() == "target"
try:
importlib.metadata.version("ling-aspec-target")
target_role = True
except importlib.metadata.PackageNotFoundError:
pass
help_requested = arguments in (["--help"], ["-h"])
if not help_requested and module in {"aspec.train_entry", "aspec.commands.quickstart"} and target_role:
raise SystemExit("Run training from the Draft venv (pip install ling-aspec); "
"this interpreter belongs to Target")
if module == "aspec.inference.service" and not target_role and not help_requested:
raise SystemExit("Start aspec target in the Target environment (pip install ling-aspec-target)")
try:
execute([sys.executable, "-m", module, *arguments])
except (ValueError, OSError) as error:
Expand Down
Loading
Loading