First off, thank you for considering contributing to d9d!
We aim to build a distributed training framework that is efficient, hackable, and reliable. To maintain this balance, we adhere to a strong engineering culture: strict type-checking, rigorous linting, and a structured proposal process for major changes.
This document outlines the standards and workflows for contributing to the project.
Before starting work on a major feature, we highly recommend jumping into our Discord server to discuss your approach with the core maintainers!
d9d uses Poetry for dependency management and packaging. You will need Python 3.11+.
-
Clone the repository:
git clone https://github.com/d9d-project/d9d.git cd d9d -
Install dependencies:
# Install all dependencies but optional ones requiring manual builds poetry install --without compat-local-overrides # Install pre-commit hooks poetry run pre-commit install
-
(Optional) Install dependencies requiring manual builds:
# If you want to develop functional requiring optional dependencies # that have to be built manually - just build the optional packages # (you may find them in pyproject.toml), put into `./packages` directory # and run this: poetry install --with compat-local-overrides
We provide a Makefile to automate common development tasks.
| Command | Description |
|---|---|
make test |
Runs both local unit tests and distributed torchrun tests. NOTE: currently distributed tests require a 8-GPU setup. |
make lint |
Runs ruff formatting, ruff linter checks and type checking using ty. |
make mkdocs |
Starts a local documentation server at localhost:8081. |
d9d is a foundational tool; architectural mistakes are expensive. Therefore, we follow the D9D Enhancement Proposal (DEP) process for significant changes.
You need a DEP if:
- You are making breaking changes to the public API.
- You are introducing a major new distributed strategy or base module.
You DO NOT need a DEP if:
- You are fixing bugs.
- You are adding a new model implementation using existing APIs.
- You are improving documentation or internal performance (without API changes).
👉 Read DEP-0001: The DEP Process for details on how to draft, propose, and implement a DEP.
We enforce strict quality standards to keep the codebase maintainable.
They are not enforced by tooling, but PRs that violate them may be asked to change.
- Composition over inheritance: components are small single-responsibility classes wired together (see
d9d/loop/component/). Avoid "God classes" that accumulate unrelated responsibilities, avoid speculative base classes that exist only to hoard "common" code. - Define contracts structurally. Use a
typing.Protocolfor a trait - a secondary capability bolted onto a type that already has its own base class (e.g.ModuleLateIniton annn.Module), where you want duck-typed conformance without forcing inheritance. Use anabc.ABCwhen the interface is the object's primary identity and the hierarchy is the "main" type (e.g.PipelineSchedule). - No reflection where it can be avoided. Avoid
getattr/hasattr/inspectand string-name dispatch. Prefer an explicitmatch-case, a proper interface, or a factory. Reflection is acceptable only when introspection is intrinsic to the feature itself - i.e. declarative registration APIs that cannot work without it, such as a@subscribe/@registerdecorator wiring handlers by signature. - Inject dependencies; don't reach for them. Components receive their collaborators as constructor arguments and store them as private fields. Don't pull them from globals/singletons or construct them internally - wiring happens at the edges (
d9d/loop/run/). - Reuse before reinventing. If PyTorch or the stdlib already solves it, use it, rather than hand-rolling an equivalent.
- No needless indirection. Don't add a wrapper that only forwards to another function/object without adding meaning. Inline it instead.
- Validate eagerly, fail fast. Validate constructor args up front; raise if a method is called outside its required lifecycle scope rather than silently misbehaving.
- Decide behavior from explicit inputs, not inferred state. Drive branching with an explicit parameter, not by sniffing the shape/dtype/contents of the data. Inferred checks silently encode invariants the caller and the next reader won't know are there - make them part of the signature instead.
- Validate at the boundary; trust within it. Data crossing an untrusted boundary (user config, deserialized state) is validated once at the edge into a model that guarantees its own invariants — that's the validation layer, and we use
pydanticfor it. Pass trusted internal data as plaindataclassesand assume it is already valid. Don't re-validate trusted internal data, and don't pass unvalidated raw input deeper than the edge. - Separate configuration from behavior. Config objects describe; classes behave. Don't merge them into one dataclass that needs
__post_init__magic. - Polymorphism for configurable objects via discriminated unions. When a configurable object has selectable behavior, model the choices as a Pydantic discriminated union and resolve them in a
build_*()factory with an exhaustivematch (case _: raise).
We use Ruff for both linting and formatting.
Configuration is strict (see pyproject.toml for the authoritative list of enabled rules).
The rule set is broad. The conventions below summarize what it means in practice so you can write conforming code without memorizing rule codes.
- Double quotes, 4-space indentation, 120-char line length.
- Imports are auto-sorted (
I). Runmake lintto fix ordering.
- Annotate everything public (
ANN): function args and return types.Nonereturns may be omitted. typing.Anyis allowed (ANN401is off) but should be a last resort.- Prefer modern syntax (
UP,FA):X | YoverOptional/Union, builtin generics (list[int]), andfrom __future__ import annotationswhere it helps.
snake_casefor functions/variables,PascalCasefor classes,UPPER_CASEfor constants.- Exceptions:
F(fornn.functional) andBLOCK_SIZE/uppercase kernel args are allowed. - Private attributes should be prefixed with
_:self._something = ...
- Every package needs
__init__.py(INP);/example/is exempt. __init__.pyfiles expose the package's public surface via an explicit__all__re-export list.
- Use idiomatic
pytest(PT):pytest.raises, fixtures, parametrization. - Tests relax several rules:
assertis allowed, no docstrings/annotations required, private access and non-top-level imports are fine.
We use ty to ensure type safety.
- Coverage:
- Core Code: strict type checking is enabled.
- Tests: Excluded from strict type analysis (
test/**) - at least for now. - External Kernels: External low-level kernels and wrappers (e.g.,
d9d/kernel/cce,deep_ep) are explicitly ignored.
We have two tiers of tests:
- Local (
-m local): Standard logic tests that run in a single process. - Distributed (
-m distributed): Tests that strictly requiretorchrun.
Requirement: All PRs must pass make test. If you add a feature, you must add corresponding tests.
We follow the Google Python style for docstrings.
- Style: Use Google-style docstrings (
Args:,Returns:,Raises:, etc.). - No type annotations in docstrings: Types are already declared in the signature and checked by
ty. Do not repeat them in the docstring. - Document
__init__: Write a docstring even for__init__, but keep it short and to the point, e.g."""Constructs the ``Trainer`` object.""". - Public API coverage: Always write docstrings for everything considered public API.
The site is built with Zensical.
- Location: Page sources live in
docs/; the code they document lives ind9d/. - Building: Run
make mkdocsto preview changes locally. - Registering pages (
zensical.toml): The site navigation is not auto-generated from thedocs/directory - it is defined explicitly in thenavtable ofzensical.toml. Whenever you add, remove, rename, or move a page underdocs/, you must updatenavaccordingly. New top-level subsystems should also be added to the appropriate section (and mirrored indocs/toc.md).
We use Semantic Release to automate versioning and changelogs. Your commit messages must follow the Conventional Commits specification.
<type>(<scope>): <subject>
| Type | Description | Version Bump |
|---|---|---|
feat |
New feature | Minor |
fix |
Bug fix | Patch |
perf |
Performance improvement | Patch |
docs |
Documentation only | None |
style |
Formatting | None |
refactor |
Code change that neither fixes a bug nor adds a feature | None |
test |
Adding missing tests, refactoring tests | None |
chore |
Build process, dependency updates | None |
ci |
CI configuration changes | None |
feat(moe): add deepep communication supportfix(checkpoint): fix async dcpdocs: update contributing guide
Before submitting a PR, ensure you have:
- Created a DEP (if the change is major).
- Added tests for your change.
- Ran
make lintto fix formatting, imports and check for typing issues. - Ran
make testto ensure no regressions. - Used a Conventional Commit title for your PR.
Happy Hacking! 🚀