What this is: A rehearsal guide for presenting PR #22703 (Bazel torch migration proof-of-concept) to the DevEx squad and broader team. Not a technical reference β that’s the deep-dive post. This post is about what to say, how to say it, and what questions to expect.
After reading this, you should be able to:
- Explain the PR at three different depths (30 sec / 2 min / 5 min)
- Copy-paste Slack messages for common situations
- Answer the 8 most likely questions without freezing
- Know which details to skip and which to emphasize
The Elevator Pitches
30 seconds β standup / async update
“PR 22703 is ready for review. It migrates item_response_theory to Bazel β the first torch-dependent package. The key thing: it establishes the pattern for ALL torch packages going forward. One new macro called
py_torch_test, Docker-based CI job, both CI checks green. After this merges, migrating the next torch package is a checklist, not a project.”
What to emphasize: “first torch package” + “establishes the pattern” + “CI green” What to skip: Everything about how it works internally
2 minutes β sprint review / broader sync
“So we had this blocker in the Bazel migration β torch. Can’t add it as a normal pip dependency because it’s 2GB, needs CUDA, and has platform-specific wheels. The solution is a two-tier architecture.
Tier one: normal tests run hermetically, same as before. No Docker, fast.
Tier two: torch tests run inside a Docker container where torch is pre-installed as a system package. We use a Bazel rule called
py_venv_testthat lets Python see system packages β so torch is visible at runtime even though Bazel doesn’t manage it directly.The PR adds one new macro β
py_torch_testβ that wraps all of this. CI routes tests to the right job using tags. item_response_theory is the proof-of-concept. Both CI jobs passed first try.The important part: this pattern is now reusable. Next torch package is just a checklist β add a BUILD file, tag the tests, push.”
What to emphasize: The problem (torch can’t be a pip dep), the two tiers, the macro, and the reusability What to skip: MODULE.bazel toolchain details, antlr4 override, gazelle directives
5 minutes β technical deep-dive with the squad
Use the 2-minute version as a base, then add:
“Let me walk through how it actually works.
The core trick is the
-IflagοΌPythonηι离樑εΌοΌ. Normalpy_testpasses-I, which blocks system packages.py_venv_teststrips that flag and creates a venv that inherits system site-packages. So when your test doesimport torch, Python finds it in Docker’s/usr/local/lib/python3.12/site-packages/.For Bazel to use Docker’s Python instead of downloading its own, we register a local toolchain in MODULE.bazel. It points at
/usr/local/bin/python3, withon_failure = skipso macOS just falls through to the hermetic interpreter.The Gazelle side is a bit counterintuitive. We add
# gazelle:ignore torchto the import line β but that doesn’t mean we’re ignoring torch. It means we’re telling the BUILD file generator to skip it. Torch still works from the system. It’s a build-time annotation, not a runtime one.CI runs two parallel jobs against the same test universe. One excludes
torch-tagged tests, the other includes onlytorch-tagged tests. Every test ends up in exactly one job.I picked item_response_theory because it’s the simplest torch-dependent package β two source files, one test. It proved the full pattern with zero noise.”
What to emphasize: The -I flag story, the two CI jobs, why this specific package was chosen
What to skip: antlr4/sdist details (unless asked), pytest_runner internals, Label() vs string paths
Slack Message Templates
PR ready for review
π¦ PR ready: #22703 β Migrate item_response_theory to Bazel (torch pattern proof-of-concept)
This is the first torch-dependent package migrated to Bazel. It establishes the reusable pattern for all torch packages:
- New
py_torch_testmacro β wrapspy_venv_testwith system site-packages- Docker-based CI job for torch-tagged tests
- Tag-based routing: hermetic job skips torch, Docker job runs only torch
Both CI jobs green β 13 files, 172 insertions, 1 commit
The pattern is documented in the macro and BUILD files β migrating the next package is a checklist.
Would appreciate a review when you get a chance π
Reply to “what does gazelle:ignore do?”
gazelle:ignore torchtells Gazelle (the BUILD file generator) to not add@pypi//torchto the deps list. It’s a build-time annotation only.Torch still works at runtime β it comes from Docker’s system Python. Without the annotation, Gazelle would try to add torch as a pip dependency, which is the exact thing we’re avoiding (2GB download, CUDA, etc).
Think of it as: “hey Gazelle, I know this import exists, and I know you can’t find it in
@pypi//. Trust me, it’ll be there at runtime.”
Reply to “why not just add torch as a pip dep?”
Three reasons torch can’t be a normal
@pypi//dependency:
- Size β ~2GB wheel. Downloading per-build is a non-starter
- Platform β CPU vs CUDA wheels, different CUDA versions, Linux vs macOS. The pip resolution gets really messy
- sdist deps β torch’s ecosystem has packages distributed as sdist-only (no pre-built wheels). aspect_rules_py can only install wheels, can’t build from source
So torch lives in Docker as a system package. Deterministic environment, controlled CUDA version, no per-build download.
Anticipated Questions β Rehearsal Scripts
Read through these as if you’re in a meeting. Practice saying your answers out loud. Focus on the branching responses β those are the high-value rehearsals.
“Why can’t torch be a normal pip dependency?”
Teammate asks something like: “Can we just pip install torch in Bazel like we do with numpy?”
Your answer:
“Not really β three problems. Torch is 2GB, so downloading it every build is too slow. It’s platform-specific β you need different wheels for CPU vs CUDA, different CUDA versions. And some of its transitive deps are sdist-only, which our Bazel Python rules can’t build. So instead, we put torch in the Docker image and let the test see it as a system package.”
(if they push back: “but can’t we cache the download?”)
“Even cached, the resolution is messy. Different CI runners might need different variants β CPU for unit tests, CUDA 12.4 for GPU tests. And the sdist problem doesn’t go away with caching. Docker gives us a clean boundary: the image IS the version pin.”
“What does gazelle:ignore actually do?”
Teammate asks: “I see gazelle:ignore torch in the code β does that mean torch is disabled?”
Your answer:
“No, the opposite β torch still works fine at runtime.
gazelle:ignoreis a note to the BUILD file generator, not to Python. It says: don’t add@pypi//torchto the dependency list. Because torch comes from the Docker image’s system Python, not from Bazel’s pip mechanism.”
(if they look confused:)
“Think of it like thisοΌζδΈͺζ―ζΉοΌβ if Bazel’s package manager is a delivery service,
gazelle:ignoresays ‘don’t order torch for delivery, it’s already in the fridge.’ The import still works, it just comes from a different place.”
“Why py_venv_test instead of py_test?”
Teammate asks: “Why the different test rule? What’s wrong with py_test?”
Your answer:
“
py_testalways runs Python in isolated mode β the-Iflag. That means Python only sees packages Bazel manages. It can’t see system packages at all. Which is great for hermeticity, but it means Python can’t see Docker’s torch.
py_venv_testhas a flag calledinclude_system_site_packagesthat strips the-Iflag. So Python creates a venv that inherits system packages. That’s how torch becomes visible.”
(if they ask “what’s the -I flag?”):
“It’s Python’s isolated modeοΌι离樑εΌοΌ. When you start Python with
-I, it ignores everything on the system βPYTHONPATH,site-packages, all of it. Only sees what you explicitly pass. Bazel uses it for reproducibility, but it blocks us from seeing system torch.”
“Is py_venv_test a private API? Is that risky?”
Teammate asks: “I noticed the bzl-visibility disable comment. Is this going to break?”
Your answer:
“Yes, it’s a private API β we’re aware of the tradeoff. The public
py_testhas no way to disable isolated mode. There’s no flag for it. So we use the private rule.If aspect_rules_py refactors their internals, our import path might break. But the fix would be a single line change in one file β
defs.bzl. We marked it with the disable comment so it’s explicit and easy to find.”
(if they ask “should we contribute upstream?”):
“That’s a good idea for later. For now, the private API gets us unblocked. An upstream contribution would mean waiting for a release cycle, and we have 30+ packages waiting to migrate.”
“What happens on macOS?”
Teammate asks: “I develop on a Mac. Will this break my local bazel test?”
Your answer:
“Nope. Two safety nets. First, the torch tests have
target_compatible_with = Linux only, so on macOS they’re silently skipped β not failed, just skipped. Second, the local toolchain in MODULE.bazel hason_failure = skip, so if/usr/local/bin/python3doesn’t have torch, Bazel falls back to its hermetic interpreter. You don’t need to do anything.”
“How do I migrate MY torch package next?”
Teammate asks: “I have a package that depends on torch. What do I do?”
Your answer:
“Five steps:
- Add
# gazelle:ignore torchto any file that imports torch- Write BUILD.bazel files β use
py_torch_testfor the tests. Copy item_response_theory as a template- Add your directory to
GAZELLE_DIRSinbazel/BUILD.bazel- If your package needs extra system deps β like
transformersorflash-attnβ add them toDockerfile.torch-test- Push and see if CI passes
The macro, the runner, the toolchain, the CI pipeline β all already in place. You’re just plugging in.”
“Why this package first?”
Teammate asks: “Why item_response_theory and not something bigger?”
Your answer:
“It’s the simplest torch-dependent package. Two source files, one test file. Direct torch usage β no transitive torch deps through hydra or datadog. Zero extra Docker deps needed. Tests passed first try in CI. The goal was to prove the pattern with minimum noise, so that the review focuses on the architecture, not on a hundred package-specific issues.”
“Isn’t this over-engineered? One macro, a runner, a Dockerfile…”
Teammate asks: “This seems like a lot of machinery for one test.”
Your answer:
“It’s not for one test β it’s for ALL torch tests. Right now there’s 30+ packages waiting on this pattern. The macro is 20 lines. The pytest runner is 26 lines. The Dockerfile was already there, just needed two more
pip installlines.Without the macro, every torch test BUILD file would need about 15 lines of boilerplate instead of 5. The runner exists because
py_venv_testdoesn’t supportpytest_main. Each piece has a specific reason.”
(if the concern is specifically about the macro count):
“Just to be clear β we only added ONE new macro:
py_torch_test. Thepy_testmacro already existed before this PR. The pytest runner is a script, not a macro.”
Quick Concept Refreshers
If someone uses a term and you need a quick mental recall, scan this section.
Hermetic vs non-hermetic Python in Bazel
- HermeticοΌε―ε°ηοΌ: Bazel downloads its own Python, uses
-Iflag. Only Bazel-managed packages visible. Reproducible, nothing leaks from host. - Non-hermetic: Uses system Python. Can see everything installed on the machine. Less reproducible, but that’s okay when the machine is a version-controlled Docker image.
- This PR: Hermetic for normal tests, non-hermetic (but Docker-controlled) for torch tests.
py_test vs py_venv_test
py_test= public API from aspect_rules_py. Always passes-I. Supportspytest_main = True. This is the default.py_venv_test= private rule. Supportsinclude_system_site_packages. Does NOT supportpytest_main. We use this for torch tests.- Think of
py_venv_testas the engine under the hood ofpy_test, with more knobs exposed.
Toolchain registration
- Bazel normally downloads its own Python interpreter.
local_runtime_repotells Bazel: “use this specific Python binary instead.”- Registered BEFORE the hermetic toolchain β first-match-wins β Docker uses system Python, macOS falls through to hermetic.
Tag-based CI routing
- Tests get tags (like
torch,integration). - CI jobs use
--test_tag_filtersto select/exclude by tag. - Hermetic job:
--test_tag_filters=-torch(everything except torch) - Docker job:
--test_tag_filters=torch(torch only) - Every test ends up in exactly one job. Clean separation.
gazelle:ignore vs gazelle:resolve vs gazelle:exclude
| Directive | What it does | When to use |
|---|---|---|
gazelle:ignore |
“Don’t add this import to deps” | Torch β it comes from system, not @pypi// |
gazelle:resolve |
“This import maps to THIS Bazel target” | Non-standard layouts where Gazelle guesses wrong |
gazelle:exclude |
“Don’t auto-generate targets for these files” | Test files that use a custom macro instead of py_test |
Mental Models
The bouncer analogy β the -I flag
The -I flag is a bouncer at the door of Python’s runtime. It blocks system packages from entering. Normal Bazel tests have this bouncer.
py_venv_test with include_system_site_packages=True fires the bouncer. System packages can walk right in. But we only fire the bouncer inside Docker, where we control exactly who’s in the building.
The two-lane highway β CI routing
βββββββββββββββββββββββββββββββββββββββββββ
β CI Trigger β
ββββββββββββββββββββ¬βββββββββββββββββββββββ€
β Hermetic Lane β Docker Lane β
β (fast, no Docker)β (torch, Docker) β
β β β
β py_test β py_torch_test β
β -I flag ON β -I flag OFF β
β @pypi// only β @pypi// + system β
β tags: -torch β tags: torch β
β β β
β numpy β β numpy β β
β scipy β β scipy β β
β torch β β torch β (from Docker)β
ββββββββββββββββββββ΄βββββββββββββββββββββββ
Two lanes, same highway, same codebase. Tag-based routing. Every test is in exactly one lane.
The “delivery vs fridge” model β gazelle:ignore
@pypi// is a delivery service. Bazel orders packages and they show up.
gazelle:ignore torch says: “don’t order torch for delivery. It’s already in the fridge (Docker’s system Python).”
The import import torch still works β Python checks the fridge (system site-packages). Gazelle just doesn’t need to put it on the shopping list (BUILD deps).
What NOT to Say
These are common phrasings that will confuse teammates. Avoid them.
| β Don’t say | β Say instead | Why |
|---|---|---|
| “We ignored torch” | “We told the BUILD generator to skip torch. Torch still works from Docker.” | “Ignored” sounds like torch is disabled |
| “We use a private API” (without context) | “We use py_venv_test, which is internal to aspect_rules_py. Conscious tradeoff β no public alternative for this.” |
Without context, “private API” sounds reckless |
| “The -I flag” (to non-Bazel people) | “Python’s isolation mode that blocks system packages” | Nobody outside Bazel-land knows what -I is |
| “Hermetic” (to non-build-system people) | “Isolated” or “sandboxed” | “Hermetic” is jargon |
| Deep-dive into antlr4/sdist | Only if specifically asked | It’s a workaround for a transitive dep. Confusing side quest. |
| Explain MODULE.bazel toolchain registration proactively | Only if someone asks “how does Bazel find system Python” | Most people don’t care how the interpreter is selected |
| “There are multiple macros” | “There’s ONE new macro: py_torch_test. The rest was already there.” |
The “too many abstractions” concern is easy to trigger |
Cheat Sheet β The Whole PR in One Table
Print this. Glance at it before the meeting.
| Aspect | One-liner |
|---|---|
| What | Migrate item_response_theory to Bazel β first torch-dependent package |
| Why | Proves the reusable pattern for ALL torch packages |
| Core trick | py_venv_test strips -I flag β Python sees system torch |
| New macro | py_torch_test β wraps py_venv_test + auto-tags torch |
| CI routing | Two parallel jobs, tag-filtered: hermetic (-torch) and Docker (torch) |
| macOS | Silently skipped β target_compatible_with = Linux, on_failure = skip |
| Package choice | Simplest torch package. 2 files, 1 test, zero noise. |
| Unlocks | 30+ torch packages can now follow the same checklist |
| Stats | 13 files, 172 insertions, both CI jobs green β |
| Risk | py_venv_test is private API β 1 line to fix if it breaks |