[feat] bump to 1.4.0: torch 2.13.0 / torchrec 1.8.0 / fbgemm 1.8.0 - #641
Conversation
Moves the whole stack forward together: torchrec 1.8.0 imports fbgemm_gpu.tbe.config unguarded, so fbgemm-gpu 1.7.0 is no longer a viable floor, and fbgemm 1.8.0 in turn targets torch 2.13. triton stays at 3.7.1 -- torch 2.13.0 pins the same version and the same upstream commit as 2.12.1, so the repacked wheel carrying the H20 ptxas fix remains valid. torch-tensorrt moves to 2.13.0, which brings TensorRT 11.0 into the cu130 image. The cu129 variant is dropped: fbgemm 1.8.0 no longer publishes cu129 wheels, and dynamicemb had never shipped one. cpu, cu126 and cu130 remain. dynamicemb and fbgemm_gpu_hstu move out of requirements/extra.txt into requirements/cu126.txt and requirements/cu130.txt so the GPU images and the new tzrec[cu126] / tzrec[cu130] extras carry them; extra.txt keeps only the RTP torch_fx_tool helper. Both wheels are rebuilds of the same upstream source commit against the new torch/CUDA, so no API change follows them. tzrec/acc/aot_utils.py drops _backport_pt178147_int_array_dedup: the int_array dedup cache in CppWrapperCpu.codegen_int_array_var now keys on the underlying IndentedBuffer upstream, and the local guard could not detect that, so keeping the patch would have silently reinstated the older implementation. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BT6xK7cczW2iAD8uMNxvuU
…the 2.13 stack Two independent regressions surfaced by the 1.4.0 dependency bump. torch 2.13 taught `torch._library.utils.is_impure` to resolve an `OpOverloadPacket` to its default overload before checking schema mutability. FX records `call_function` targets as packets, so the zero-user `fbgemm.bounds_check_indices` node a quantized lookup leaves behind is now impure and `eliminate_dead_code` keeps it. In a dense graph that node is unreachable from the output but still pins the whole sparse KJT subtree, so the dense graph went on reading raw feature keys -- which are absent from the sparse model's output dict it is fed at export time, hence `KeyError: '<feature>.values'`. A dense graph is only ever the subgraph feeding its output, so prune it by reachability, anchoring placeholders and the output; the sparse-side callers keep default semantics because the lookup belongs there. torch_tensorrt 2.13.0 published on the pytorch-wheels index as `+cu130` ships no C++ runtime, which makes `TorchTensorRTModule.setup_engine` fall back to a pure Python engine object. 2.13 also dropped the `engine is None` guard from the hot path, so passing that object to `tensorrt::execute_engine` under `torch.jit.trace` failed as `unsupported input type: PyObject` instead of anything diagnosable. Take torch_tensorrt from PyPI, which does carry the runtime. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BT6xK7cczW2iAD8uMNxvuU
The registry rejects overwriting an existing tag, so the rebuilt cu130 image (which picks up the PyPI torch_tensorrt carrying the C++ runtime) needs a fresh tag rather than replacing tzrec-test:1.4-cu130. Bump DOCKER_TAG_SUFFIX and point the workflows at the new tags for this validation round; promote_docker.sh still publishes them to tzrec-devel:1.4-<device> without the suffix. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BT6xK7cczW2iAD8uMNxvuU
# Conflicts: # tzrec/version.py
Picks up master's graphlearn 1.3.9 / pyodps>=0.13.1 pins and the pyre-to-pyrefly switch, which are baked into the images through requirements. Registry tags are immutable, so the rebuild needs a fresh suffix rather than replacing -u1. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BT6xK7cczW2iAD8uMNxvuU
Matches the host every other pinned wheel already uses. Same object: the accelerate and cn-beijing endpoints return an identical ETag and size. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BT6xK7cczW2iAD8uMNxvuU
`tzrec/ops/benchmarks/hstu_attention_bench.py` builds its CLI with click but nothing required it. Until now pyre-check pulled click in transitively, so the import resolved by accident; replacing pyre-check with pyrefly dropped that edge and pyrefly reports the module as missing. Require click directly so the benchmark works from a clean environment rather than by coincidence. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BT6xK7cczW2iAD8uMNxvuU
Bakes click into the images so the HSTU attention benchmark runs from a clean container, rather than only after the PyTyping job installs requirements. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BT6xK7cczW2iAD8uMNxvuU
| dlc submit pytorchjob \ | ||
| --name=${JOB_NAME} \ | ||
| --command='pip install tzrec==${TZREC_NIGHTLY_VERSION} -f http://tzrec.oss-accelerate.aliyuncs.com/release/nightly/repo.html --trusted-host tzrec.oss-accelerate.aliyuncs.com | ||
| --command='pip install tzrec[cu126]==${TZREC_NIGHTLY_VERSION} -f http://tzrec.oss-accelerate.aliyuncs.com/release/nightly/repo.html --trusted-host tzrec.oss-accelerate.aliyuncs.com |
There was a problem hiding this comment.
This dlc submit command installs tzrec[cu126], but the --worker_image below (line 151) is the bare tzrec-devel:${TZREC_DOCKER_VERSION} tag, which scripts/promote_docker.sh defines as an alias of the cu130 image. Before this PR the command was device-agnostic (pip install tzrec==), so the bare tag was harmless; now the extras are device-specific — tzrec[cu126] pulls the cu12 builds of faiss, dynamicemb and fbgemm_gpu_hstu, which pip would install over the cu130 image's preinstalled cu130 versions, breaking them at import time (no CUDA 12 runtime in that image).
Suggest pinning the worker image to tzrec-devel:${TZREC_DOCKER_VERSION}-cu126 here to match the cu126 PAI image the rest of this tutorial uses.
| graph.eliminate_dead_code( | ||
| is_impure_node=lambda node: node.op in ("placeholder", "output") | ||
| ) |
There was a problem hiding this comment.
The fix itself looks correct (anchors only placeholders/output; all 4 dense call sites converted, the 3 sparse-side ones correctly keep default DCE), but there is no CPU-runnable regression guard for it, so the next torch impurity change would only be caught by the GPU-lane e2e tests.
The existing CPU tests are structurally blind to this regression: they trace sparse embeddings as leaf modules and don't quantize, so the dead subtree contains only pure nodes that plain DCE removes on any torch version; and their serving_data is built from dict(batch.to_dict()), which still carries raw <feature>.values/.lengths keys — an unpruned dense graph runs fine in-test and KeyErrors only in real serving. (_run_dense_graph_sanity_check also skips running the pruned graph.)
A cheap CPU-lane tripwire: after build_dense_graph_module/split_model, assert no node in the dense graph does a getitem on a raw feature key (or has an fbgemm-target call_function), and/or run the scripted model on serving-shaped input built strictly from the dense_graph_config keys. The export_distributed_embedding path has the same gap and additionally no e2e test in any lane.
Code review summaryReviewed statically across five dimensions (code quality, performance, test coverage, documentation accuracy, security). Overall the bump is in very good shape. Verified clean:
Two inline comments:
Two minor notes (not blocking):
|
… example The generated dlc command pointed at tzrec-devel, which the console does not offer as a node image; use the same PAI torcheasyrec image the tutorial selects above it, and pass the 535 driver the resource section already calls for. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BT6xK7cczW2iAD8uMNxvuU
# Conflicts: # requirements/runtime.txt # tzrec/version.py
Picks up master's pyfg 1.0.6 and feature-store-py 2.2.10 pins, which are baked into the images through requirements. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BT6xK7cczW2iAD8uMNxvuU
The validation images were promoted, so tzrec-devel:1.4 now holds the same content the -u4 tags were checked against. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BT6xK7cczW2iAD8uMNxvuU
hstu/library.py only loads its .so when torch.cuda.is_available() is true, while hstu/hstu_ops_gpu.py registers fakes for fbgemm::hstu_varlen_fwd_* unconditionally. On a GPU image whose driver cannot serve the bundled CUDA runtime the ops are therefore never registered and the fake registration raises RuntimeError, which the ImportError-only guard let escape -- taking down every entry point, since main.py imports aot_utils, which imports this module, at module scope. Catch any exception so an unusable hstu degrades to the PyTorch/Triton kernel the way a missing one already did. Before 1.4.0 the wheel lived in extra.txt and was absent from the images, so the import failed with ImportError and the narrow guard sufficed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BT6xK7cczW2iAD8uMNxvuU
cu129 was dropped for 1.4.0 because fbgemm 1.8.0's tbe_training_backward .so overflowed the linker's 2 GiB PC32 range: CUDA 12.9 packs the same SASS into a noticeably larger .nv_fatbin than CUDA 13 does. Building it with --compress-mode=size brings it back under the limit, at the cost of requiring an R550.54.14+ driver, so the wheels exist again. That fills a real gap between the other two images: cu126 runs on R525 but stops at sm_90, while cu130 reaches Blackwell but needs R580. cu129 covers the same architectures as cu130 on a 550 driver. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BT6xK7cczW2iAD8uMNxvuU
Cuts the 1.4.0 release: torch 2.13.0 / torchrec 1.8.0 / fbgemm_gpu 1.8.0, with
torch-tensorrt 2.13.0 (TensorRT 11.0) in the cu130 image. triton stays at 3.7.1 —
torch 2.13.0 pins the same version and the same upstream triton commit as 2.12.1,
so the repacked wheel carrying the H20 ptxas fix is still the right one.
The three bumps have to move together: torchrec 1.8.0 imports
fbgemm_gpu.tbe.configunguarded, so fbgemm-gpu 1.7.0 stops being a viable floor, and fbgemm 1.8.0 in turn
targets torch 2.13.
cu129 kept, on a rebuilt toolchain
cu129 was initially dropped here: fbgemm 1.8.0's
tbe_training_backward.so overflowsthe linker's 2 GiB PC32 range under CUDA 12.9, which packs the same SASS into a
noticeably larger
.nv_fatbinthan CUDA 13 does (cu130 is itself already at 96.3% ofthat limit). Rebuilding the wheels with nvcc
--compress-mode=sizebrings it backunder, at the cost of requiring an R550.54.14+ driver — so the variant stays.
It fills a real gap between the other two images: cu126 runs on R525 but stops at
sm_90, while cu130 reaches Blackwell but needs R580. cu129 covers cu130's
architectures on a 550 driver. The docs now lead with a driver table, since driver —
not compute capability — is the first thing to choose on:
-cu126-cu129-cu130They also note that the op wheels ship no PTX, so an unlisted architecture cannot JIT
fall back, and that
LD_LIBRARY_PATH=/usr/local/cuda-13/compatlets cu130 run on anolder driver via the bundled cuda-compat.
dynamicemb / fbgemm_gpu_hstu move out of extra.txt
Both now live in
requirements/cu126.txtandrequirements/cu130.txt, so the GPUimages and the new
tzrec[cu126]/tzrec[cu130]extras carry them instead ofrequiring a separate manual install.
requirements/extra.txtkeeps only the RTPtorch_fx_toolhelper, whichscripts/ci/ci_test.shstill installs on its own.Both wheels are rebuilds of the same upstream source commit against the new
torch/CUDA (
dynamicemb6b94bbf,fbgemm_gpu_hstufece651b), so no API changefollows them.
Compatibility audit
A source-level audit of every place TorchEasyRec copies, subclasses or monkeypatches
upstream internals —
plan_util.py(enumerator/estimator/calculate_shard_storages),dynamicemb_util.py(to_sharding_plan,HardwarePerfConfig.get_device_bw,ShardPerfContext.build_shard_perf_contexts, the jagged-tensor validator),optim/optimizer.py(apply_split_helper),dist_util.py(DMP, train pipelines,mc-ebc
input_dist) andexport_util.py(RTP fbgemm op remaps,_all_keys_used_once)— found no signature or import breakage. fbgemm's
apply_split_helperbody isbyte-identical between 1.7.0 and 1.8.0, and every
torch.ops.fbgemm.*schemaTorchEasyRec calls is unchanged.
tzrec/acc/aot_utils.pydrops_backport_pt178147_int_array_dedup:pytorch/pytorch#178147 landed in 2.13.0, and the patch's guard only recognised its own
marker attribute, so leaving it in would have silently reinstated the older
codegen_int_array_varover the upstream fix.Three behaviour regressions the bump surfaced
The first two were caught by CI on the first run; the third by a user report.
Dense graphs stopped being prunable. torch 2.13 taught
torch._library.utils.is_impureto resolve anOpOverloadPacketto its defaultoverload before checking schema mutability. FX records
call_functiontargets aspackets, so the zero-user
fbgemm.bounds_check_indicesnode a quantized lookup leavesbehind is now impure and
eliminate_dead_codekeeps it. In a dense graph that node isunreachable from the output but still pins the whole sparse KJT subtree, so the dense
graph went on reading raw feature keys — which are absent from the sparse model's
output dict it is handed at export time, hence
KeyError: '<feature>.values'acrossevery AOT/TRT export test. A dense graph is only ever the subgraph feeding its output,
so
_eliminate_dense_dead_codeprunes it by reachability, anchoring placeholders andthe output; the sparse-side callers keep default semantics because the lookup
legitimately belongs there.
The cu130 torch-tensorrt wheel has no C++ runtime.
torch_tensorrt-2.13.0+cu130on the pytorch-wheels index is a 620 KB Python-only build (2.12.1's was 3.87 MB with
libtorchtrt.soand the_Cextension), soENABLED_FEATURES.torch_tensorrt_runtimeis False and
setup_engine()falls back to a pure-Python engine object. 2.13 alsodropped the
engine is Noneguard fromforward(), so handing that object totensorrt::execute_engineundertorch.jit.tracefailed asunsupported input type: PyObjectrather than anything diagnosable. The plain PyPI wheel does carry theruntime, so the Dockerfile takes it from there instead of the pytorch-wheels index.
An unusable hstu wheel took down every entry point.
hstu/library.pyonly loads its.so when
torch.cuda.is_available()is true, whilehstu/hstu_ops_gpu.pyregistersfakes for
fbgemm::hstu_varlen_fwd_*unconditionally — so on a GPU image whose drivercannot serve the bundled CUDA runtime, the fake registration raises
RuntimeError. Theguard in
cutlass_hstu_attention.pycaught onlyImportError, and sincemain.pyimports
aot_utils(and thus this module) at module scope,python -m tzrec.train_evaldied at import. This is exposed by the packaging change above: before 1.4.0 the wheel
lived in
extra.txtand was simply absent from the images, so the import failed withImportErrorand the narrow guard sufficed. It now catches any exception and degradesto the PyTorch/Triton kernel. Verified that a container with no usable GPU imports and
runs on CPU.
One latent bug on master, fixed here
tzrec/ops/benchmarks/hstu_attention_bench.pybuilds its CLI withclick, but nothingever required it —
pyre-checkhappened to pull it in transitively, so the importresolved by accident. Replacing pyre-check with pyrefly drops that edge, and this is the
first image built from the new requirements, so
pyrefly checkreports the module asmissing.
clickis now a declared dependency. This is not caused by the bump: masterwill hit it too as soon as its image is rebuilt.
Other
Pre-commit hooks move to ruff v0.16.4 and codespell v2.4.3 (mdformat deliberately
left at 0.7.22). Docs are updated for the new stack, the dropped cu129 image, and the
tzrec[cu130]/tzrec[cu126]install commands. The DLC tutorial's generateddlc submitcommand now uses the PAI officialtorcheasyrecimage the tutorial alreadytells you to select, instead of
tzrec-devel, and passes the 535 driver its resourcesection calls for. The PPU image is untouched and still tracks the vendor 1.2 stack.
Test Plan
pre-commit run -aclean;pyrefly checkreports 0 errors on the merged branch.(GPU), Unit Test CPU and Unit Test H20 all pass.
tzrec-test:1.4-cu130container: 8 pass with the dense-graph fix, and the 2 TRT onespass once torch-tensorrt carries its runtime.
python tzrec/tests/run.py --scope h20on real 8×H20 hardware(torch 2.13.0+cu130 / torchrec 1.8.0 / fbgemm 1.8.0+cu130 / triton 3.7.1):
94 tests, all pass — this is what covers the CUTLASS/WGMMA HSTU kernels.
1.4-cu130-u4(and the same checks on the cu129 image): torch 2.13.0+cu130, torchrec 1.8.0, fbgemm1.8.0+cu130, triton 3.7.1, torch-tensorrt 2.13.0 with TensorRT 11.0.0.114 and
runtime=True, plus master's graphlearn 1.3.9 / pyfg 1.0.6 / feature-store-py 2.2.10,and click / dynamicemb / hstu / faiss all importing.
pyrefly checkalso runs cleaninside that image.
Workflow YAMLs point at
tzrec-test:1.4-u4for this validation round and are revertedto
tzrec-devel:1.4once the images are promoted. The registry rejects overwriting anexisting tag, which is why the validation images carry a
-u<n>suffix; promotionpublishes them to
tzrec-devel:1.4-<device>without it.🤖 Generated with Claude Code
https://claude.ai/code/session_01BT6xK7cczW2iAD8uMNxvuU