Jesse A. Green / notebook

Live demo: the DocCompiler guide library as a static, read-only snapshot (October 2026). No backend, no accounts, nothing to submit.

d2l-zh Administrator & Developer Guide

Generated May 2026 from d2l-ai/d2l-zh at e6b18cc

AI-generated documentation. The library is checked by sampling (three repositories so far); this guide was not individually reviewed. Verify against the project before relying on it.

Administrator, maintainer, and contributor documentation for the Chinese edition of Dive into Deep Learning (动手学深度学习) — covering build pipeline, multi-framework code extraction, LaTeX post-processing, CI infrastructure, and fork strategy.


1. Architecture Overview

1.1 What this repo is, operationally

d2l-ai/d2l-zh is not a conventional software project. It is an open, reproducible textbook: source Markdown chapters that contain executable code cells, which are compiled by a custom book-build tool (d2lbook) into:

  • A browsable HTML website (https://zh-v2.d2l.ai/)
  • Four framework-specific PDFs (MXNet, PyTorch, TensorFlow, PaddlePaddle)
  • A downloadable ZIP of Jupyter notebooks
  • A pip-installable helper Python package (d2l) generated from annotated code cells

Every chapter renders four parallel implementations of the same deep-learning concepts — one per supported framework — from a single Markdown source. The d2lbook tool handles the fan-out. This design is what makes the repository operationally unusual and what most of this guide addresses.

1.2 The two surfaces

d2l-zh ships two distinct artifacts from one source tree:

                         source tree
                             │
        ┌────────────────────┼────────────────────┐
        │                                         │
  chapter_*/                                  d2l/
  (Markdown + code cells)                     (Python package)
        │                                         │
        ▼                                         ▼
 ┌──────────────────┐                     ┌──────────────────┐
 │   d2lbook        │                     │   setup.py       │
 │   - evaluates    │                     │   - packages     │
 │   - renders HTML │                     │   - publishes    │
 │   - renders PDF  │                     │   - versioned    │
 │   - zips nb's    │                     │   - pinned deps  │
 └────────┬─────────┘                     └────────┬─────────┘
          │                                        │
          ▼                                        ▼
  zh-v2.d2l.ai/*                           pip install d2l
  zh-v2.d2l.ai/d2l-zh.pdf                  (4 frameworks, one wheel)
  zh-v2.d2l.ai/d2l-zh.zip

The two are linked: the d2l Python package is partially generated from the chapter code cells via the #@save annotation. A reusable function defined inline in a chapter (e.g., load_data_fashion_mnist) is tagged with #@save and automatically collected by d2lbook build lib into d2l/<framework>.py. Readers then import d2l.torch as d2l and call d2l.load_data_fashion_mnist(...) from their own notebooks.

This loop (book code ↔ library code) is the core architectural idea:

  1. The book teaches the function.
  2. The book uses the function in later chapters.
  3. The d2l package exports the function to the reader's own code.

Note

You cannot refactor a #@save-annotated function purely as a library maintainer. The function's name, signature, and semantics are published to readers both textually (in the book) and programmatically (via pip). Breaking changes require a version bump and coordinated edits to many chapters.

1.3 Component inventory

Component Path Role
Book content chapter_*/ 19 chapters of Markdown + executable code cells
Translation refs chapter_*/*_origin.md English source snapshots for sync
Python package d2l/ Four framework backends (mxnet, torch, tensorflow, paddle)
Package metadata setup.py Defines d2l on PyPI
Build config config.ini d2lbook master switchboard
LaTeX post-proc static/post_latex/main.py Chinese typography fixes
CI entry ci/submit-job.py AWS Batch job submission
Docker images ci/docker/ Per-framework containers
Diagrams (source) graffle/ OmniGraffle originals
Diagrams (rendered) img/ PNG/SVG used by chapters
Web assets static/ Logo, favicon, frontpage
Bibliography d2l.bib BibTeX for PDF references
Style guides STYLE_GUIDE.md, TERMINOLOGY.md Formatting and translation consistency
Contrib contrib/ Draft chapters (excluded from build)

1.4 Technology stack

Layer Technology
Source format Markdown + d2lbook directives (:begin_tab:, #@tab, #@save, :numref:)
Build tool d2lbook (PyPI, reads config.ini)
HTML renderer Sphinx (pulled in by d2lbook)
PDF chain Sphinx → LaTeX (CTEX, Source Han fonts) → XeLaTeX → PDF
CI orchestration GitHub Actions (pull_request_target)
CI compute AWS Batch (GPU for eval, CPU for assembly)
Container registry AWS ECR
Hosting S3 + CloudFront (zh-v2.d2l.ai)
Package distribution PyPI (pip install d2l)

1.5 At-a-glance stats

Property Value
Repository type Executable textbook (Markdown + code cells)
Source commit documented e6b18cc (2023-08-17)
Total source files 819
Book version 2.0.0
Python package version d2l 2.0.0
License (code / content) MIT-0 (code), CC-BY-SA-4.0 (content)
Supported frameworks MXNet, PyTorch, TensorFlow, PaddlePaddle
Python package deps (pinned) jupyter==1.0.0, numpy==1.21.5, matplotlib==3.5.1, requests==2.25.1, pandas==1.2.4
Build tool d2lbook (external, installed from PyPI)
CI GitHub Actions → AWS Batch (ECR-hosted Docker)
Deployment S3 + CloudFront (zh-v2.d2l.ai), four PDFs, notebook ZIP
Origin repo d2l-ai/d2l-en (English source)

1.6 Build pipeline (end to end)

   source .md (with :begin_tab:<fw> code cells)
        │
        │  d2lbook build eval --tab <fw>  ← runs per framework on GPU
        ▼
   _build/eval_<fw>/*.md (with evaluated outputs embedded)
        │
        │  d2lbook build html  (Sphinx HTML theme)
        │  d2lbook build pdf --tab <fw>  (Sphinx LaTeX → post_latex/main.py → xelatex)
        │  d2lbook build pkg  (collects #@save cells into d2l/<fw>.py)
        ▼
   _build/html/  ,  _build/pdf/d2l-zh.pdf  ,  d2l-*.whl
        │
        │  d2lbook deploy html pdf pkg colab sagemaker slides --s3 s3://zh-v2.d2l.ai/
        ▼
   CloudFront → https://zh-v2.d2l.ai/

1.7 CI architecture

GitHub push/PR
     │
     ▼
.github/workflows/ci.yml  (GitHub Actions, ubuntu-latest runner)
     │
     │  for each framework (torch, tf, mxnet, paddle):
     │    use ./.github/actions/submit-job → ci/submit-job.py → boto3 AWS Batch
     │
     ▼
AWS Batch  (D2L-CI-GPU queue,  job definitions d2l-ci-zh-gpu-<fw>:N)
     │
     │  each GPU job:
     │    pull ECR image (e.g. d2l-zh-torch)
     │    d2l_job.sh clones repo, restores S3 cache, runs build_<fw>.sh
     │    build_<fw>.sh: d2lbook build eval --tab <fw>; slides; upload cache
     │
     ▼
After all four GPU jobs succeed → build_and_deploy (CPU queue, D2L-Builder image)
     │
     │  d2lbook build pdf  (mxnet default)
     │  d2lbook build pdf --tab pytorch
     │  d2lbook build html
     │  if JOB_NAME == d2l-zh/release:
     │      d2lbook build pkg
     │      d2lbook deploy ... --s3 s3://zh-v2.d2l.ai/
     │  else:
     │      d2lbook deploy html pdf --s3 s3://preview.d2l.ai/<JOB_NAME>/
     │
     ▼
Live site / PR preview comment with preview URL

Three key trade-offs baked in:

  • Evaluation is cached in S3 (s3://preview.d2l.ai/<cache_dir>/<repo>-<branch>/_build/). A chapter only re-evaluates if its modification time differs from the cached copy. git timesync in d2l_job.sh restores modification times from git metadata because cloning resets them.
  • CI is split CPU/GPU by function. Per-framework eval runs on GPU (torch/tf/mxnet/paddle); final HTML/PDF assembly runs on CPU. Separation keeps GPU minutes minimal.
  • The SAFE_TO_USE_SCRIPT parameter gates untrusted PRs (see §7 Security). For PRs from forks without write permission, the CI overwrites the submitted workflow_scripts/ with the master copy to prevent code execution via PR.

1.8 Red flags a new maintainer should know immediately

  • MXNet is end-of-life upstream. Apache archived MXNet in late 2023. The d2l/mxnet.py backend and MXNet PDF builds will continue working for historical/reference purposes, but MXNet is not a target for new feature development.
  • d2l==0.17.6 is hardcoded in CI build scripts, not d2l==2.0.0. This is deliberate — the CI installs an old published d2l package for legacy compatibility while the source tree in the repo becomes the next published d2l package.
  • Code cells tagged #@save are load-bearing. Removing one changes the public API of the d2l package. Never delete a #@save cell without checking downstream usage.
  • Framework tabs must stay in sync. Every :begin_tab:<framework> block must have equivalent implementations across all four frameworks, or d2lbook build tabcheck fails in CI.
  • The CI pipeline requires AWS Batch with GPU compute environments. A fork that wants the full build pipeline will need to replicate this infrastructure or replace it.

2. System Requirements

2.1 For building locally (contributor)

Tool Purpose Notes
Python ≥ 3.5 All tooling 3.9 recommended in practice
d2lbook Book build tool Install via pip (see INFO.md)
One framework (MXNet, PyTorch, TF, or Paddle) For d2lbook build eval Only needed if you evaluate notebooks; otherwise not required for prose edits
Jupyter d2lbook dependency pip install jupyter==1.0.0 per setup.py
Sphinx HTML/LaTeX backend (pulled in by d2lbook) Not a direct install
XeLaTeX + CTEX + Source Han fonts PDF build only sudo apt install texlive-full texlive-xetex fonts-wqy-microhei on Debian-family, plus manual Source Han install
OmniGraffle Diagram editing only macOS-only; graffle/ source is not required to build the book
Git Source control Standard

2.2 For running CI (platform operator)

Service Use
AWS Batch GPU + CPU compute environments
AWS ECR Hosts the four Docker images (d2l-zh-torch, -tf, -mxnet, -paddle, d2l-builder)
AWS S3 Build cache (preview.d2l.ai/ci_cache_*), preview site (preview.d2l.ai/<JOB>/), production site (zh-v2.d2l.ai/)
AWS IAM OIDC GitHub Actions → AWS credentials (see id-token: write in ci.yml)
GitHub Actions Workflow dispatch (ubuntu-latest runners for orchestration only; heavy work runs on Batch)

2.3 Framework version matrix (as of commit e6b18cc)

Determined from ECR Dockerfiles and print_versions.py:

Framework Base image Installed version Extras
PyTorch nvcr.io/nvidia/pytorch:22.06-py3 (PyTorch 1.12, May 2022) torch, torchvision, gym, gpytorch, syne_tune GPU, CUDA included
TensorFlow (TF image, see Dockerfile.d2l-zh-tf) tensorflow, tensorflow_probability GPU
MXNet (MXNet image) mxnet (legacy, frozen) GPU
Paddle (Paddle image) paddle GPU
Builder (CPU base) d2lbook==1.0.0 CPU only, used to assemble HTML/PDF

2.4 Reader-facing runtime

The d2l package pins:

jupyter==1.0.0
numpy==1.21.5
matplotlib==3.5.1
requests==2.25.1
pandas==1.2.4

These are the pins for the helper package, not for readers' framework choice. The tight pins exist because the book's evaluated notebook outputs were generated against these versions; newer versions may produce different tensor shapes, warning messages, or plot styling that diverges from the printed/rendered book.


3. Configuration Reference

3.1 config.ini — the master switchboard

d2lbook reads config.ini for every build action. The file is organized into sections, each controlling a pipeline stage.

Important

config.ini is the single most operationally significant file in the repository. Changes here affect all four framework builds, HTML output, PDF rendering, deployment targets, and the d2l package itself. Always test a full build cycle after modifying it.

[project]

Key Value at e6b18cc Effect
name d2l-zh Output filenames (d2l-zh.pdf, d2l-zh.zip)
title 动手学深度学习 Cover title in HTML & PDF
author Aston Zhang, Zachary C. Lipton, Mu Li, and Alexander J. Smola Title page
copyright 2022, All authors. Licensed under CC-BY-SA-4.0 and MIT-0. Colophon
release 2.0.0 Edition identifier
lang zh Language tag used in d2lbook deploy S3 paths (${lang}-v2.d2l.ai/)

[translation]

Key Value Effect
origin_repo d2l-ai/d2l-en Upstream English source repo
origin_lang en Source language
translator aws Translation provider tag

[build]

Key Effect
notebooks = *.md */*.md Markdown files at repo root and one level down are evaluated as Jupyter notebooks.
resources = img/ d2lzh/ d2l.bib setup.py Copied verbatim into _build/. Note d2lzh/ is legacy — the current package is d2l/.
exclusions = */*_origin.md README.md STYLE_GUIDE.md INFO.md CODE_OF_CONDUCT.md CONTRIBUTING.md contrib/*md Skipped.
eval_notebook = True Execute notebooks during build (set False for a fast structure-only build).
tabs = mxnet, pytorch, tensorflow, paddle The four frameworks that fan out from each source chapter.
sphinx_configs Inline Sphinx config: numbering format (图%s, 表%s, %s节), full LaTeX preamble with CTEX, Source Han fonts, fancy page style, custom verbatim colors.

exclusions is where you add a chapter-in-progress that should not yet compile.

[html]

header_links defines the navbar (PDF download, Jupyter ZIP, course link, GitHub, English edition). favicon and html_logo point to static/ assets.

[pdf]

Key Effect
post_latex = ./static/post_latex/main.py Post-processor run on the Sphinx-generated .tex before xelatex.
latex_logo = static/logo.png Title page logo.
bibfile = d2l.bib BibTeX file for references.

[library] and [library-<framework>]

  • [library] version_file = d2l/__init__.py — d2lbook reads __version__ from this file.
  • [library-<fw>] lib_file = d2l/<fw>.py — the file where #@save cells are collected.
  • [library-<fw>] lib_name = <name> — the import alias (np for mxnet, torch for torch, tf for tensorflow, paddle for paddle).
  • simple_alias, fluent_alias, alias, reverse_alias — four classes of rewrite rules that transform d2l.foo(...) calls in Markdown code cells into framework-native calls.

[deploy]

  • other_file_s3urls — legacy ZIPs to include in release artifacts (the 1.0, 1.1, 2.0.0 notebook bundles).
  • google_analytics_tracking_id = UA-96378503-2 — embedded in generated HTML.

[colab] and [sagemaker]

Define derivative notebook repos for each framework:

Framework Colab repo SageMaker repo
mxnet d2l-ai/d2l-zh-colab d2l-ai/d2l-zh-sagemaker
pytorch d2l-ai/d2l-zh-pytorch-colab d2l-ai/d2l-zh-pytorch-sagemaker
tensorflow d2l-ai/d2l-zh-tensorflow-colab d2l-ai/d2l-zh-tensorflow-sagemaker
paddle d2l-ai/d2l-zh-paddle-colab d2l-ai/d2l-zh-paddle-sagemaker

These are push-only targets. Never edit them directly; they are regenerated every release.

[slides]

Key Value
github_repo = pytorch, d2l-ai/d2l-zh-pytorch-slides Slide-deck output repo (PyTorch only)

3.2 Environment variables affecting CI

Variable Source Effect
SOURCE_REF submit-job.py --source-ref Git ref to check out (defaults to master).
WORK_DIR --work-dir Directory to cd into before running COMMAND.
SAFE_TO_USE_SCRIPT --safe-to-use-script When false, d2l_job.sh discards PR-supplied workflow scripts and uses master's.
ORIGINAL_REPO --original-repo (default d2l-zh) Used to construct clone URL.
ORIGINAL_ORG positional arg 9 in d2l_job.sh (default d2l-ai) Hardcoded in production to prevent org-swap attacks.
DISABLE_CACHE read inside build_*.sh When true, skips S3 cache restore (forces full re-evaluation).

3.3 Local configuration overrides

d2lbook does not natively support a config.local.ini override file. To do per-environment config:

  • Option A: Checkout a separate branch with a modified config.ini (ephemeral, never push).
  • Option B: Use shell-level env vars consumed by build_*.sh (e.g., DISABLE_CACHE=true).

There is no per-user personal-config file; the canonical config.ini is always the one in the repo.


4. Deployment & Scaling

4.1 Branch model

Branch Purpose
master Default. PRs land here. CI runs on every push; preview-only deploys.
release Fast-forward-only from master when a release is cut. Triggers production deploy + d2lbook build pkg.

4.2 Deployment flow

build_and_deploy.sh branches on JOB_NAME:

if [[ "$JOB_NAME" == "$REPO_NAME/release" ]]; then
  d2lbook build pkg
  d2lbook deploy html pdf pkg colab sagemaker slides --s3 "s3://${LANG}-v2.d2l.ai/"
else
  d2lbook deploy html pdf --s3 "s3://preview.d2l.ai/${JOB_NAME}/"
fi

Release path (pushed to release branch):

  • Builds the d2l wheel (d2lbook build pkg).
  • Deploys HTML, PDFs, wheel, Colab notebooks, SageMaker notebooks, and slides to s3://zh-v2.d2l.ai/.
  • CloudFront fronts the S3 bucket; updates are live within minutes (subject to CF cache invalidation).

Preview path (any other branch or PR):

  • Deploys only HTML and PDFs to s3://preview.d2l.ai/<JOB_NAME>/.
  • GitHub Actions posts a comment on the PR with the preview URL.

Tip

Use [skip frameworks] in commit messages for prose-only PRs where re-evaluating notebooks is wasteful. This skips all four GPU evaluation jobs and goes straight to the build-and-deploy assembly using cached outputs.

4.3 Cutting a release

  1. Bump d2l/__init__.py __version__ on master (semantic version — major if API breaks, minor for new chapters/#@save functions, patch for prose/figures).
  2. Merge all pending content PRs into master.
  3. Verify master CI is green across all four frameworks and the builder job.
  4. git checkout release && git merge --ff-only master && git push (fast-forward only; never force-push release).
  5. Release-branch CI triggers production deploy.
  6. (External, manual) Publish the wheel to PyPI with twine upload.
  7. Tag the repo: git tag v2.0.0 && git push --tags.

4.4 Derivative notebook repos

Four Colab repos and four SageMaker repos are pushed on every release. These are regenerated from the build output. Do not cherry-pick into them. If an external contributor PRs into one of these repos, redirect them to d2l-zh itself.

4.5 Legacy ZIPs

config.ini [deploy] other_file_s3urls lists three legacy bundles:

s3://d2l-webdata/releases/d2l-zh/d2l-zh-1.0.zip
s3://d2l-webdata/releases/d2l-zh/d2l-zh-1.1.zip
s3://d2l-webdata/releases/d2l-zh/d2l-zh-2.0.0.zip

Do not delete on rotation — they are referenced by external courses, papers, and books.

Note

Deployed URLs: https://zh-v2.d2l.ai/ (live HTML), https://zh-v2.d2l.ai/d2l-zh.pdf (MXNet PDF), https://zh-v2.d2l.ai/d2l-zh-pytorch.pdf (PyTorch PDF), https://zh-v2.d2l.ai/d2l-zh.zip (notebook ZIP), https://preview.d2l.ai/d2l-zh/PR-<N>-<SHA>/ (PR previews).


5. Operations

5.1 Local build

From INFO.md:

# HTML
make html

# PDF (requires XeLaTeX + Chinese fonts)
make pdf

make targets map to d2lbook build commands. A typical local workflow for prose-only edits:

d2lbook build html          # no eval, for fast iteration
d2lbook build eval --tab pytorch   # eval only PyTorch tab
d2lbook build pdf --tab pytorch    # PDF for PyTorch tab

To build everything the way CI does:

# per framework
d2lbook build eval --tab mxnet
d2lbook build eval --tab pytorch
d2lbook build eval --tab tensorflow
d2lbook build eval --tab paddle

# final assembly
d2lbook build html
d2lbook build pdf          # default tab (mxnet)
d2lbook build pdf --tab pytorch
d2lbook build pkg

Output lands in _build/.

Tip

For a single-chapter rebuild: d2lbook build eval --tab pytorch --md chapter_linear-networks/*. d2lbook incrementally evaluates based on modification time — editing one .md and rebuilding only runs that file.

5.2 Sanity checks

Run during CI before per-framework evaluation:

d2lbook build outputcheck tabcheck
  • outputcheck: ensures no Jupyter cell output has been committed accidentally (the book's outputs are always regenerated by CI).
  • tabcheck: ensures every :begin_tab:<fw> section has a matching implementation for all frameworks declared in [build] tabs.

A tabcheck failure means you added or edited a multi-framework code section without updating all four tabs.

5.3 CI entry points

Five jobs, four parallel + one gated:

  1. build_torch → AWS Batch → build_pytorch.sh (evaluate PyTorch tab)
  2. build_tf → AWS Batch → build_tf.sh
  3. build_mxnet → AWS Batch → build_mxnet.sh
  4. build_paddle → AWS Batch → build_paddle.sh
  5. build_and_deploy (needs all four above) → AWS Batch → build_and_deploy.sh (HTML + PDFs + conditional deploy)

Note

Commit message skip tags: [skip torch], [skip tf], [skip mxnet], [skip paddle], [skip frameworks] (all four evals), [skip builder] (final assembly).

5.4 ci/submit-job.py — Batch job submission

CLI flags (argparse):

Flag Type Default Meaning
--profile str None AWS profile name
--region str us-west-2 AWS region
--name str d2l-ci AWS Batch job name
--job-type str ci-cpu Queue/definition selector
--source-ref str master Git ref to check out
--work-dir str . Directory inside repo
--command str git rev-parse HEAD | tee stdout.log Command executed inside container
--remote str https://github.com/d2l-ai/d2l-zh Git remote
--safe-to-use-script flag false Trust PR-supplied workflow_scripts?
--original-repo str d2l-zh Repository name
--wait flag false Block until Batch job finishes
--timeout int 7200 Job timeout in seconds (2 hours)

Job type → queue mapping:

--job-type Queue Definition
ci-cpu D2L-CI-CPU d2l-ci-cpu-builder:2
ci-gpu-torch D2L-CI-GPU d2l-ci-zh-gpu-torch:1
ci-gpu-tf D2L-CI-GPU d2l-ci-zh-gpu-tf:1
ci-gpu-mxnet D2L-CI-GPU d2l-ci-zh-gpu-mxnet:1
ci-gpu-paddle D2L-CI-GPU d2l-ci-zh-gpu-paddle:1

5.5 Inside the Batch container — d2l_job.sh

ci/docker/d2l_job.sh is the container entrypoint:

git clone https://github.com/"$ORIGINAL_ORG"/"$ORIGINAL_REPO".git
cp -R "$ORIGINAL_REPO"/.github/workflow_scripts .

cd "$ORIGINAL_REPO"
git remote set-url origin $REMOTE
git fetch origin $SOURCE_REF:working
git checkout working

# Restore mtimes from git so S3 cache keys are stable
git timesync *.md **/*.md

# Untrusted PR guard
if [[ ${SAFE_TO_USE_SCRIPT,,} != true ]]; then
    rm -rf .github/workflow_scripts
    mv ../workflow_scripts .github/
fi

cd $WORK_DIR
/bin/bash -o pipefail -c "eval $COMMAND"

Key points:

  • git timesync resets file modification times after a fresh clone so the S3 evaluation cache is valid.
  • Workflow script replacement prevents untrusted PRs from injecting code into build scripts.

5.6 Cache operations

Two caches:

  1. S3 evaluation cache (s3://preview.d2l.ai/ci_cache_{pr,push}/<repo>-<branch>/_build/): per-framework evaluated .md files. Keyed by file mtime.
  2. Docker layer cache (ECR): built by .github/workflows/build-docker.yml on demand.

To invalidate: .github/workflows/clear-cache.yml is a manual workflow dispatch.


6. Performance

6.1 Build time optimization

The primary performance lever is the S3 evaluation cache. Chapters only re-evaluate when their modification time changes. git timesync ensures clones restore original mtimes.

Optimization Effect
S3 cache hit Skip chapter eval entirely (saves GPU minutes)
[skip <fw>] tags Skip specific framework evals for prose-only changes
[skip frameworks] Skip all evals, use cached outputs for HTML/PDF assembly
Split CPU/GPU GPU only used for notebook execution; assembly runs on cheap CPU

6.2 Reader-side performance

The d2l helper library uses get_dataloader_workers() to set data-loading parallelism:

def get_dataloader_workers():
    return 0 if sys.platform.startswith('win') else 4

On Windows, multi-process data loading is disabled (returns 0 workers) because Windows lacks fast fork. On Linux/macOS, 4 worker processes are used.


7. Security

7.1 Untrusted PR handling — SAFE_TO_USE_SCRIPT

The primary threat model: a contributor submits a PR from a fork that modifies .github/workflow_scripts/build_torch.sh to exfiltrate AWS credentials or deploy malicious content.

Mitigation in d2l_job.sh:

if [[ ${SAFE_TO_USE_SCRIPT,,} != true ]]; then
    rm -rf .github/workflow_scripts
    mv ../workflow_scripts .github/   # master's copy
fi

SAFE_TO_USE_SCRIPT should be set to true only when the PR author has write access to d2l-ai/d2l-zh.

Warning

Never edit d2l_job.sh to bypass the SAFE_TO_USE_SCRIPT guard for convenience. If a PR fails for lack of access, rebase it from a trusted branch inside the org. Removing this guard exposes AWS credentials to arbitrary code execution via PR.

7.2 pull_request_target — know what it means

ci.yml uses pull_request_target (not pull_request). This event fires with the secrets and permissions of the target repository, and checks out the base branch by default. The d2l-zh workflow checks out the PR's SHA into the Batch job ($SOURCE_REF) but executes the build scripts from master (via SAFE_TO_USE_SCRIPT=false). The GitHub Actions runner itself only does orchestration — it never runs PR code.

7.3 Organization pinning

ORIGINAL_ORG=${9:-'d2l-ai'}
# TODO @anirudhdagar: hardcode ORIGINAL_ORG

The d2l-infra repo defines the Batch job definition arguments and does not expose position 9 as a configurable input — so in production ORIGINAL_ORG is effectively pinned to d2l-ai. The TODO flags the desire to remove the default and make it fail loudly if not set.

Caution

The default fallback in ORIGINAL_ORG=${9:-'d2l-ai'} means an attacker who controls the Batch job parameter 9 could redirect the clone. Production is safe because the Batch job definition doesn't expose this parameter, but the code-level guard should be hardened by removing the default.

7.4 Secret handling

The workflow uses OIDC (permissions: id-token: write) to assume an AWS role without long-lived secrets stored in GitHub. No AWS access keys in the repo. The IAM role attached to the Batch job has scoped access to:

  • The CI cache S3 bucket
  • The ECR repositories for the framework images
  • The preview.d2l.ai S3 bucket

Release deploys to zh-v2.d2l.ai run under a potentially broader IAM role. Audit quarterly.

7.5 No telemetry from reader code

The shipped d2l package contains no telemetry. setup.py pins five widely-used packages and declares nothing network-facing beyond requests. Readers running the notebooks locally never phone home.

Google Analytics UA-96378503-2 is injected into the HTML build only (config.ini [deploy]). It tracks web visits to zh-v2.d2l.ai, not reader code execution.

7.6 Content licensing

  • Code (d2l/, chapter code cells, build scripts): MIT-0 per setup.py.
  • Text and diagrams: CC-BY-SA-4.0 per config.ini [project] copyright.

Forks that modify content must retain attribution and share-alike under CC-BY-SA-4.0.


8. Developer Internals

8.1 The d2l Python package

Package layout

d2l/
├── __init__.py        # Holds __version__ = "2.0.0"
├── mxnet.py           # MXNet backend (import d2l.mxnet as d2l)
├── torch.py           # PyTorch backend
├── tensorflow.py      # TensorFlow backend
└── paddle.py          # PaddlePaddle backend

Each backend file is generated (or manually synchronized) so that all four expose the same Python API surface.

#@save — how book code becomes library code

In any chapter, a code cell annotated with #@save is automatically collected into the library:

#@save
def load_data_fashion_mnist(batch_size, resize=None):
    """Download the Fashion-MNIST dataset and load it into memory."""
    # ...

When d2lbook build pkg runs, it scans all chapters, extracts every #@save-annotated definition, and writes them to d2l/<framework>.py (per tab).

Important

Do not add #@save casually. Each annotation is an implicit public API commitment. Any rename or signature change is a breaking change for reader notebooks, later chapters, and downstream forks. The version in d2l/__init__.py must be bumped for API changes.

Alias system — how d2l.foo(...) becomes framework-native

Four classes of alias rules live in config.ini [library-<fw>]:

simple_alias — direct attribute mapping:

  • d2l.ones(...) → np.ones(...) (MXNet) / torch.ones(...) (PyTorch)
  • d2l.tensor(...) → np.array(...) (MXNet) / torch.tensor(...) (PyTorch)

fluent_alias — method calls on the first argument:

  • d2l.reshape(x, shape) → x.reshape(shape) (PyTorch)
  • d2l.numpy(x) → x.detach().numpy() (PyTorch) / x.asnumpy() (MXNet)

alias — arbitrary Python expressions:

  • d2l.nn_Module → nn.Block (MXNet) / nn.Module (PyTorch) / tf.keras.Model (TF) / nn.Layer (Paddle)

reverse_alias — regex substitutions on evaluated output.

Version pinning

config.ini [library] version_file = d2l/__init__.py tells d2lbook where the version lives. d2lbook build pkg reads this, tags the wheel, and publishes with that version. Do not edit setup.py's version directly; it reads from d2l.__version__.

8.2 LaTeX post-processing (static/post_latex/main.py)

Chinese typography requires fixes that Sphinx's LaTeX writer does not handle natively. The script mutates the .tex file in place with three transformations:

_unnumber_chaps_and_secs(lines)

NUM_UNNUMBERED_CHAPS = 3   # Preface, Installation, Notation
TOC2_START_CHAP_NO   = 5   # Preliminaries onward
  • First three \chapter{...} → \chapter*{...} (unnumbered but in TOC).
  • All sections within those chapters are likewise starred.
  • From chapter 5 onward, TOC depth drops to 2 (suppresses subsections in PDF TOC).
  • \section{小结..., \section{练习... are unnumbered everywhere (summaries and exercises).

_sec_to_chap(lines)

Regex rewrites {Section \ref{\detokenize{...chapter_.../index:...}}} to Chapter \ref. Uses the regex module for recursive balanced braces.

Commented-out functions

_edit_titlepage (blank date on title page) and _delete_discussions_title (remove forum QR codes) are both disabled. Dead code — either uncomment or delete.

8.3 Content conventions

These conventions affect build correctness, not just style:

Framework tabs: every framework-specific code block is wrapped in :begin_tab:<fw> / :end_tab: directives. d2lbook build tabcheck fails if any tab is missing.

#@save rules:

  • Always place #@save at the top of the cell, first line after the opening fence.
  • Don't rename #@save-marked functions in later chapters — collisions silently overwrite.

Cross-references:

  • Section labels: sec_<name>. Referenced with :numref:.
  • Chapter labels: chap_<name> on index.md. Referenced with :numref:.
  • Figure labels: fig_<name>. Must use :numref: for "图 3.2" style numbering.
  • Equation labels: eq_<name>. Must use :eqref:.

No Jupyter outputs in source: d2lbook build outputcheck fails if any .md contains embedded notebook output.

8.4 Translation workflow

File Role
chapter_<x>/<y>.md Chinese translated content (built).
chapter_<x>/<y>_origin.md Snapshot of the English source at the time of last translation. Excluded from build.

When upstream d2l-ai/d2l-en updates a section:

  1. Translator pulls the new English version.
  2. Diffs against the local _origin.md to see what changed.
  3. Applies the diff to the Chinese .md.
  4. Overwrites _origin.md with the new English source.
  5. Opens a PR.

8.5 Diagrams — OmniGraffle

Diagrams live in graffle/ (source) and img/ (rendered PNG/SVG). OmniGraffle is macOS-only. Fork operators without macOS can replace with draw.io or Mermaid — commit both source and rendered image.

8.6 Class hierarchy (126 classes)

The d2l package across all four backends defines these major class families:

Class Purpose Backends
Timer Elapsed time measurement All 4
Accumulator Running sums across minibatches All 4
Animator Live-updating notebook plots All 4
Vocab Token→index vocabulary All 4
SeqDataLoader Sequence data iterator All 4
RNNModelScratch / RNNModel From-scratch and framework RNN wrappers All 4
Encoder / Decoder / EncoderDecoder Seq2seq abstractions All 4
Seq2SeqEncoder GRU-based encoder All 4
AdditiveAttention / DotProductAttention Attention scoring functions All 4
MultiHeadAttention Multi-head attention layer All 4
PositionalEncoding Sinusoidal position embedding All 4
PositionWiseFFN / AddNorm Transformer sub-layers All 4
EncoderBlock / TransformerEncoder Transformer building blocks All 4
Residual ResNet residual block All 4
BERTEncoder / MaskLM / NextSentencePred / BERTModel BERT pre-training All 4
BananasDataset / VOCSegDataset CV datasets All 4
TokenEmbedding Pre-trained word vectors All 4
SNLIDataset NLI dataset All 4
Benchmark Context-manager timer All 4

Each class exists independently in each backend file with framework-specific base classes (e.g., nn.Module for PyTorch, nn.Block for MXNet, tf.keras.Model for TF, nn.Layer for Paddle).


9. Integration Points

9.1 External dependencies

System Integration Direction
AWS Batch CI execution Push (submit jobs)
AWS ECR Docker images Pull (containers)
AWS S3 Cache + hosting Read/write
CloudFront CDN for zh-v2.d2l.ai Read (serves site)
PyPI d2l package distribution Push (publish)
GitHub Actions CI orchestration Bidirectional
d2l-ai/d2l-en Upstream English source Pull (translation sync)
d2lbook (PyPI) Build tool Pull (dependency)
Colab/SageMaker repos Derivative notebooks Push (generated)

9.2 Forking — Running your own edition

What you can reuse without infrastructure:

  • The entire chapter_*/ tree — edit prose.
  • config.ini — change project/deploy settings.
  • static/post_latex/main.py — edit the UNNUMBERED set for your language.
  • setup.py — change package name if forking the library.

What requires infrastructure replacement:

  • AWS Batch + ECR + S3 (your own accounts and Docker images).
  • CI job definitions (create equivalents in your AWS account).
  • Domain + CloudFront.

Alternative infrastructures:

  • GitHub Actions with GPU runners (replace submit-job with direct workflow).
  • Self-hosted Jupyter cluster for evaluation.
  • CPU-only build (skip eval_notebook; prose only).

What NOT to change without coordination:

  • The d2l package name on PyPI (owned by upstream). Pick a different name for forks.
  • Colab/SageMaker repo targets — repoint or blank out.
  • Google Analytics ID — replace with yours or remove.

10. Troubleshooting

Common CI failures

Symptom Cause Fix
tabcheck fails with "tab X missing in file Y" New section added without all framework tabs Add the missing tab; rerun CI
PyTorch eval OOMs Added a chapter with a larger model Smaller batch size in the cell, or request a larger GPU instance
Cache hit rate near zero git timesync didn't run or failed Check d2l_job.sh output; ensure git-timesync binary is in the image
XeLaTeX PDF build fails with "Font undefined" Source Han fonts missing from builder image Rebuild d2l-builder via build-docker.yml
"no such file d2l/mxnet.py" during eval MXNet tab skipped but later code imports it Remove [skip mxnet]
Preview shows stale content CloudFront edge cache Manually invalidate or wait TTL

Warning

d2lbook build pkg produces its API from evaluated output, not raw source. Running d2lbook build pkg without first running d2lbook build eval --tab <fw> will produce an incomplete or outdated library. Always eval before pkg.

d2lbook build pkg produces wrong API

  • Check that the #@save annotation is on the first line of the cell. A blank line before it defeats extraction.
  • Check that config.ini [library-<fw>] lib_file points to the right file.
  • Re-run d2lbook build pkg after d2lbook build eval --tab <fw>.

When the English source has diverged significantly

Translation-workflow drift: after a major upstream refactor, every chapter may have diverging _origin.md files.

  1. git log --oneline upstream/master -- chapter_<x>/<y>.md in the d2l-en checkout.
  2. Translate incrementally per commit, not per file, if the drift is multi-year.
  3. If a chapter was removed upstream, flag for removal and open a PR to drop both files.

Note

Graffle source without macOS: OmniGraffle is macOS-only. Use a cloud Mac (MacStadium), or replace the diagram with draw.io/Mermaid/TikZ. Commit the new source alongside the rendered image; update STYLE_GUIDE.md if this becomes policy. Do not commit half-converted .graffle files.

CI troubleshooting flowchart

CI failed?
├── One framework job failed → check build_<fw>.sh logs in AWS Batch
│   ├── "CUDA out of memory" → reduce batch_size in offending chapter
│   ├── "eval timed out" → split chapter or raise timeout in ci/submit-job.py
│   ├── "tabcheck failed"  → missing tab block; add it
│   └── "outputcheck failed" → strip committed outputs, re-commit
├── All four framework jobs green, build_and_deploy failed →
│   ├── "xelatex error" → new Chinese character without font coverage
│   │                      or mis-balanced LaTeX env from Markdown typo
│   ├── "Sphinx reference not found" → broken :numref: or :ref: label
│   └── "deploy to S3 failed" → IAM role perms; check OIDC assume-role
└── All jobs green, preview shows wrong content →
    ├── CloudFront cache (wait TTL or manual invalidation)
    └── Browser cache (hard reload)

11. Known Issues & Tech Debt

11.1 MXNet is end-of-life

Apache archived MXNet in late 2023. nvcr.io/mxnet images are no longer updated. The d2l/mxnet.py backend works with pinned versions but will degrade as CUDA drivers advance.

Mitigation options: freeze MXNet builds, remove from [build] tabs, or maintain a forked image indefinitely.

11.2 d2l==0.17.6 hardcoded in build scripts

CI installs an old published d2l for bootstrap. The source tree builds the next d2l via d2lbook build pkg. When the source tree's exports diverge from 0.17.6, compatibility issues arise.

Fix: use d2l==$(python -c "from d2l import __version__; print(__version__)") or lock to whatever was last published.

11.3 ORIGINAL_ORG default in d2l_job.sh

The default fallback should be removed and hardcoded to prevent potential org-swap attacks.

11.4 Dead code in main.py

_delete_discussions_title and _edit_titlepage are commented out. Either uncomment or delete.

11.5 numpy==1.21.5 pin is stale

NumPy 1.21.x is past its maintenance window. Readers get conflicts with newer ML libraries. Update pin to a supported version, retest all notebooks.

11.6 No test suite

d2l/ has no tests. Correctness is verified only by CI producing a successful build. Low-hanging fruit: add tests/test_d2l_api.py that imports each backend and asserts every expected function exists.

11.7 contrib/to-rm-mx-contrib-text marked for removal

Directory name literally says "to-rm". Removing it is a mechanical PR.

11.8 Brittle Dockerfile with pinned 2022 base image

Dockerfile.d2l-zh-torch uses nvcr.io/nvidia/pytorch:22.06-py3 from May 2022. Accumulates CVEs. Set a monthly calendar reminder to rebuild or automate via cron workflow.


12. Quick Reference

Command cheat sheet

# Local: fast iteration on prose (no eval)
d2lbook build html

# Local: single-framework PDF
d2lbook build eval --tab pytorch && d2lbook build pdf --tab pytorch

# Local: package build (all frameworks)
for fw in mxnet pytorch tensorflow paddle; do d2lbook build eval --tab $fw; done
d2lbook build pkg

# Sanity
d2lbook build outputcheck tabcheck

# CI: submit a Batch job manually (AWS CLI configured)
python ci/submit-job.py \
  --job-type ci-gpu-torch \
  --source-ref refs/pull/500/head \
  --safe-to-use-script \
  --command "chmod +x .github/workflow_scripts/build_pytorch.sh && .github/workflow_scripts/build_pytorch.sh d2l-zh master ci_cache_pr" \
  --wait

# CI: force preview rebuild bypassing cache
DISABLE_CACHE=true ./.github/workflow_scripts/build_pytorch.sh d2l-zh master ci_cache_pr

File map

Concern Files
Book content chapter_*/**.md, index.md
Translation state chapter_*/*_origin.md
Python package d2l/{__init__,mxnet,torch,tensorflow,paddle}.py
Package deps setup.py
Build config config.ini
Bibliography d2l.bib, chapter_references/
Diagrams graffle/**.graffle (source), img/**.{png,svg} (rendered)
Web assets static/ (logo, favicon, frontpage)
LaTeX post-proc static/post_latex/main.py
CI workflow .github/workflows/ci.yml
CI scripts .github/workflow_scripts/*.sh
Batch submit ci/submit-job.py
Docker images ci/docker/Dockerfile.d2l-*, d2l_job.sh, entry.sh, print_versions.py, git-timesync, login_ecr.sh
Style / terms STYLE_GUIDE.md, TERMINOLOGY.md, INFO.md

Entry points reference

Entry point Location Purpose
setup.py Repo root pip install -e . or pip install d2l
static/post_latex/main.py — LaTeX post-processor invoked by d2lbook
ci/submit-job.py CI AWS Batch job submitter
ci/docker/d2l_job.sh Docker images Container ENTRYPOINT
ci/docker/entry.sh Docker images Calls print_versions.py then drops into bash
ci/docker/print_versions.py Docker images Diagnostic banner at job start

Useful external projects

Project Purpose
d2l-ai/d2l-en English source of truth
d2l-ai/d2lbook The book build tool (external dependency)
d2l-ai/d2l-{zh,en}-{pytorch,tensorflow,mxnet,paddle}-colab Derivative Colab repos
d2l-ai/d2l-{zh,en}-{pytorch,tensorflow,mxnet,paddle}-sagemaker Derivative SageMaker repos
d2l-ai/d2l-zh-pytorch-slides Slide decks