Hygon DCU (DTK) is FlagGems-primary by default and additionally supports an opt-in CUDA boxing route through its dedicated FLAGOS_ACCELERATOR=dcu branch. Two properties of the vendor stack make the boxing route possible:
- The DCU
torchwheel is a hipified build: it registers HIP kernels under theCUDAdispatch key and its tensors reportDeviceType::CUDA(torch.version.cuda is None,torch.version.hip == '6.3.x'). Generated PrivateUse1 → CUDA boxing kernels dispatch intolibtorch_hip.sounchanged. - DTK ships a CUDA compatibility toolkit at
$ROCM_PATH/cuda/cuda-*whoselibcudart.so.12is a thin shim overlibgalaxyhip.so— the same runtimelibtorch_hip.souses. Runtime sources compile as-is with plain hostg++; nonvcc, nohipcc, no hipify pass.
The default build compiles the FlagGems Python path and leaves the rest of the conf on cpu_fallback, with no vendor torch package. Setting FLAGOS_BUILD_BOXING=1 builds the boxing wheel instead: the generated PrivateUse1 → CUDA boxing kernels compile and dispatch into libtorch_hip.so. FLAGOS_BUILD_VENDOR stays a no-op either way (there is no DCU backend directory to enable) and the FlagGems C++ path stays off (FLAGOS_BUILD_FLAGGEMS_CPP=OFF, since DTK ships no liboperators.so). Unless a section says otherwise, the rest of this guide describes the boxing build.
Status: Beta. CI validates vendor-backend and FlagGems-runtime operator suites, general tests, and profiler parity on DCU runners. Inference and training smoke tests are deferred pending model mount and card-count confirmation.
- Hygon DCU hardware with driver installed
- DTK (Hygon Deep Learning Toolkit) installed at
/opt/dtk(or$ROCM_PATH) - DTK torch wheel (hipified, registers kernels under CUDA dispatch key) — its device libraries are bundled at build time; see Vendor core libraries
- The official
torchwheel of the same minor version installed in the build and runtime environment (the+cpubuild is enough) - Python 3.8 or later matching the DTK torch build
cmake >= 3.18,ninja,patchelf
git clone https://github.com/flagos-ai/PyTorch-Plugin-FL.git
cd PyTorch-Plugin-FL
# Source DTK environment (exports ROCM_PATH and other variables)
source /opt/dtk/env.sh
# Default build: FlagGems-primary, no boxing kernels, no vendor libtorch
FLAGOS_ACCELERATOR=dcu pip install --no-build-isolation -vvv -e .
# Boxing build (opt-in): generated CUDA boxing kernels + bundled DTK libtorch
FLAGOS_ACCELERATOR=dcu FLAGOS_BUILD_BOXING=1 \
pip install --no-build-isolation -vvv -e .ROCM_PATH (what DTK's env.sh exports) selects the DTK root; the default is /opt/dtk. Pass it explicitly if DTK lives elsewhere.
- Boxing is opt-in: the default
FLAGOS_ACCELERATOR=dcubuild compiles the FlagGems Python path and no boxing kernels.FLAGOS_BUILD_BOXING=1adds the generated PrivateUse1 → CUDA boxing kernels (csrc/aten/generated/cuda_kernels.cc).FLAGOS_BUILD_VENDORhas no DCU backend directory andFLAGOS_BUILD_FLAGGEMS_CPP=OFFinsetup.pyin both builds. - No
nvccorhipccneeded: CUDA runtime sources compile with plaing++using DTK's CUDA compatibility toolkit headers. - MIOpen CMake config fix: DTK's exported MIOpen config bakes in
/usr/lib/x86_64-linux-gnu/librt.so, which no longer exists on glibc ≥ 2.34 (librt was folded into libc). TheFLAGOS_ACCELERATOR=dcubranch rewrites that dangling path to-lrt.
The DCU wheel bundles DTK's device libraries (libtorch_hip.so,
libc10_hip.so, libmagma.so) and runs them on the official PyTorch core.
torch_fl dlopens them before import torch; your torch installation is not
modified. Operator coverage is unchanged — the HIP kernels still register under
the CUDA dispatch key.
Two consequences worth knowing:
- The installed torch must match the minor version the bundle was built against.
A mismatch is rejected at import with the required version named, because
dlopenaccepts it silently (the symbol names match) and the failure would otherwise appear as a wrong-looking result much later. - Import order matters:
import torch_flbeforetorchin a fresh process. PyTorch caches its CUDA hooks on first import, so a later preload registers kernels that device init cannot reach.
To build the bundle:
FLAGOS_ACCELERATOR=dcu python setup.py build_ext --inplace # builds the ABI shim
bash scripts/vendor/bundle_dcu_libtorch.sh # stages DTK's device libsFLAGOS_DCU_VENDOR_CORE=1 on both commands selects the legacy mode, which
bundles DTK's full core set and selects it through a private torch facade,
leaving the installed wheel unchanged. Use it if you need DTK's private
fused ops, which are unreachable in the default mode. The two bundle layouts are
not interchangeable: selecting a mode that does not match the bundle on disk
fails at import rather than half-wiring the process.
The vendor-core path checks the vendor and front-end PyTorch three-part versions
before loading any libraries. Import torch_fl before torch so the private
facade can select the matching runtime.
Full measurements and rationale: vendor-free-core-libs.md.
python -c "
import torch, torch_fl
print(f'PyTorch version: {torch.__version__}')
print(f'torch.version.hip: {torch.version.hip}')
print(f'flagos devices: {torch.flagos.device_count()}')
# Basic computation
x = torch.randn(512, 512, device='flagos')
result = torch.mm(x, x)
print(f'mm output shape: {result.shape}')
print(f'mm matches .cuda(): {torch.allclose(result.cpu(), torch.mm(x.cpu().cuda(), x.cpu().cuda()).cpu())}')
"Expected output shows torch.version.hip: 6.3.x, flagos devices: N, and mm matches .cuda(): True.
DCU supports PyTorch's device-generic autocast and gradient scaling APIs with FP16 and BF16:
import torch
import torch_fl # noqa: F401
model = torch.nn.Linear(8, 4).to("flagos")
optimizer = torch.optim.SGD(model.parameters(), lr=0.01)
scaler = torch.amp.GradScaler("flagos")
x = torch.randn(2, 8, device="flagos")
with torch.autocast("flagos", dtype=torch.float16):
loss = model(x).square().mean()
scaler.scale(loss).backward()
scaler.step(optimizer)
scaler.update()Run the DCU AMP coverage, including both autocast dtypes and finite/non-finite GradScaler paths:
pytest tests/integration/test_amp_contract.py -m amp -vDCU torch.compile is validated with FlagTree's HCU backend on the Hygon
gfx936 target. FlagTree replaces the active triton module at installation
time, so install it into the environment that will run the kernels rather than
trying to import a flagtree module at runtime.
.github/scripts/set_env_dcu.sh installs the vendor wheel for both CI stages,
from the FlagOS resource index:
python3 -m pip uninstall -y triton # repeat until fully uninstalled
python3 -m pip install "flagtree===0.6.2a1+hcu3.6" \
--index-url=https://resource.flagos.net/repository/flagos-pypi-hosted/simpleThe wheel ships [triton.backends] hcu = triton.backends.hcu entry-point
metadata, so TRITON_BACKENDS_IN_TREE is not needed for it. Building the same
backend from source is the alternative when a newer revision is required:
git clone https://github.com/flagos-ai/FlagTree.git
cd FlagTree
export FLAGTREE_BACKEND=hcu
MAX_JOBS=16 python -m pip install . --no-build-isolation -vThese variables select the HCU backend and assert that the active Triton is FlagTree:
export FLAGTREE_BACKEND=hcu
export FLAGOS_USE_FLAGTREE=1
export GEMS_VENDOR=hygon
python -c 'import triton; from triton._flagtree_backend import FLAGTREE_BACKEND; print(triton.__version__, FLAGTREE_BACKEND)'
pytest tests/integration/test_compile.py -vThe measured Hygon run used FlagTree 0.6.0, PyTorch 2.10.0, and produced
GPUTarget(backend='hip', arch='gfx936', warp_size=64). All 15 compile tests
passed. Compiled outputs and backward gradients remained on flagos; eager
and compiled results matched. The DCU CI manifest does not include this check —
sourcing FlagTree to build the kernels is validated separately from
torch.compile, and the manifest's FlagGems groups already exercise the same
hcu backend on eager paths.
Run the main operator suite against the DCU boxing backend:
pytest tests/integration/test_allocator.py \
tests/integration/test_factory_ops.py -q
pytest tests/integration/ops/ \
-m "main_ops and not flaggems and not flaggems_python" \
-v --tb=shorttests/unit/ goes through the per-file runner instead of a single pytest call,
because one file can take the whole interpreter down (see
Running the unit suite).
Verify profiler parity with torch.cuda:
pytest tests/integration/test_profiler_parity.py \
-v -m main_ops --tb=shortSee Profiler Architecture for details on device event emission for DCU.
FlagGems runs on the hcu backend of the FlagTree Triton build, whose hygon
vendor declares device_name="cuda" — exactly what the boxing route expects. The
kernel path is Python (Triton), not the C++ wrapped FlagGems library, so
FLAGOS_BUILD_FLAGGEMS_CPP=0 and FLAGOS_BUILD_FLAGGEMS=1 are the build switches.
.github/scripts/set_env_dcu.sh performs all of the following; routing comes from
backends_dcu.conf itself, so a CI job runs the FlagGems path with no switch
involved. The steps below are what that script does, for a local install.
The DTK image's own Triton carries no hcu entry-point metadata and no
flag_gems, so both are installed explicitly. FlagGems is pinned rather than
tracking master: it moves faster than the vendor Triton it needs, and the pin is
the revision validated against flagtree 0.6.2a1+hcu3.6.
python3 -m pip uninstall -y triton # repeat until fully uninstalled
python3 -m pip install --no-deps "flagtree===0.6.2a1+hcu3.6" \
--index-url=https://resource.flagos.net/repository/flagos-pypi-hosted/simple
python3 -m pip install --no-deps \
"git+https://github.com/FlagOpen/FlagGems.git@e7b4a865fce6d85861ee91a6aca56564ef9acf7d"
python3 -m pip install packaging PyYAML==6.0.1 sqlalchemy==2.0.48 'numpy<2'--no-deps keeps pip from replacing the pinned CPU torch with a transitive
preference, and numpy<2 is required because the CPU torch wheel is built
against the NumPy 1.x ABI.
source /opt/dtk/env.sh
FLAGOS_ACCELERATOR=dcu \
FLAGOS_BUILD_FLAGGEMS_CPP=0 \
FLAGOS_BUILD_FLAGGEMS=1 \
pip install --no-build-isolation -e .No runtime variable is needed. DCU pins FLAGOS_BUILD_FLAGGEMS_CPP off at build
time (DTK has no FlagGems C++ build to link), so the build record already says
the C++ FlagGems path is absent; the flaggems_cpp-marked tests skip on that
record rather than on a variable.
GEMS_VENDOR=hygon is set automatically on a DCU build, so you no longer need to export it manually. This matters beyond FlagGems: GEMS_VENDOR also selects the comm profile (see torch_fl/comm/process_group.py), and DCU is a CUDA-ABI vendor whose ProcessGroupNCCL is RCCL underneath.
A DCU build records ACCELERATOR = "dcu" in torch_fl/_build_config.py, which selects backends_dcu.conf — no need to re-export FLAGOS_ACCELERATOR at runtime (the record is the only thing read there), and no opt-in variable exists any more.
Confirm what the interpreter actually resolved before trusting the flags:
python - <<'PY'
import torch_fl # noqa: F401 -- installs the torch.cuda shim; must precede flag_gems
import triton, triton.backends, flag_gems
assert "hcu" in triton.backends.backends, sorted(triton.backends.backends)
assert flag_gems.vendor_name == "hygon", flag_gems.vendor_name
print(triton.__version__, flag_gems.__version__, flag_gems.vendor_name)
PYbackends_dcu.conf is backends_flaggems.conf with a few ops routed back to the cuda boxing kernel. Most are hcu Triton gaps; _conj and relu/relu_ are contract cases:
silu_backward:tl.math.div_rnhas nocreate_precise_divfloweringslice_backward: output is correct standalone, but feeding that grad to MIOpen'sconvolution_backwardtriggers a hardware VMFault_conj: FlagGems'_conjmaterializes the conjugated values, while ATen'sconjis a lazy view that only sets the Conjugate bit;tests/integration/test_math_bits_contract.pypins that, and the boxing kernel keeps the view lazyrelu/relu_: FlagGems'relu_forward_kernel_rank_1displaces ATen'sat::native::vectorized_elementwise_kernelfrom the profiler-parity workload's trace, andtests/integration/test_profiler_parity.pyrequires that trace to carry anat::nativetemplate kernel; the boxing kernel is what launches one
The complete pinned set, with its per-op diagnosis, is in backends_dcu.conf's own
header note (mm/bmm -- FlagGems issue #6227 -- plus mse_loss #6221 and
transpose.int #6219).
Override per-op routing with FLAGOS_OP_<name>=flagos_python|cuda.
pytest tests/integration/ops/ \
-m "flaggems and main_ops" \
-v --tb=shortNo marker deselection needed — tests/integration/ops/conftest.py collects the flaggems-marked items unconditionally now, so leaving the mark out would silently deselect the whole group instead of failing it.
tests/unit/ runs one file per interpreter through
.github/scripts/run_unit_tests.py rather than as a single pytest tests/unit
call:
UNIT_TESTS_ALLOW_TEARDOWN_CRASH=1 \
python .github/scripts/run_unit_tests.py --timeout 300A unit test file here can take the whole interpreter down without unwinding — an invalid device address is reported by the driver as a VMFault, and the process dies before pytest can report which test was running — so in a shared interpreter one bad file costs every result collected after it, including files that had already passed. One process per file keeps the result attributable.
UNIT_TESTS_ALLOW_TEARDOWN_CRASH=1 covers DCU containers whose RDMA stack faults
during exit(): libibverbs' ELF destructor calls into libnl-route-3's
rtnl_tc_unregister, so a run that already reported every test still ends in a
dying interpreter. The tolerance applies only when pytest has printed a complete,
clean summary; a fatal signal before the summary is still a failure. Whether a
given container has that fault is a property of its image rather than of this
project, so it stays opt-in instead of being assumed either way.
Multi-card works with FlagGems enabled, over FlagCX or the RCCL fallback. The automatic GEMS_VENDOR=hygon routes ProcessGroupFlagOS to the CUDA-ABI profile (zero-copy _flagos_to_cuda_view + ProcessGroupNCCL, which on DTK is RCCL: dist.is_nccl_available() == True and torch.cuda.nccl.version() reports (2, 22, 3)).
The RCCL tier needs ProcessGroupNCCL, which the CPU-only torch==2.10.0+cpu core this wheel front-ends does not export — torch.distributed.ProcessGroupNCCL is absent, so without the bridge the tier cannot be built at all and the group falls through to host-staged gloo. On DCU that class is supplied by _flagos_nccl, a pybind factory built against the bundled lib_dcu (libc10_hip / libtorch_hip) plus librccl; since #366 the wheel packages it at torch_fl/comm/_nccl_ext/, so an installed wheel reaches RCCL with no source checkout and no build.py run. Confirm what the installed package actually carries before relying on the tier:
python -m pytest tests/integration/test_comm_native_bridge.py -vThat file is what CI runs for this tier (.github/configs/dcu.yml, the Native comm bridge group) in its wheel-only workspace, so a released wheel cannot silently lose the tier again. It asserts two things: the factory loads from inside the installed torch_fl, and a world-size-1 flagos group resolves its inner backend to nccl — with neither the FlagCX nor the staged-gloo skip reason set — and completes a collective. torch.distributed.ProcessGroupNCCL being absent from the CPU-only torch is expected and is the reason the bridge exists; the test does not assert it either way. FlagCX is tried first when flagcx is importable, so this tier is what remains when it is not.
export HSA_FORCE_FINE_GRAIN_PCIE=1 # RCCL warns when unset; affects
# multi-card throughput and stability
# Collectives + DDP (FlagGems can be on or off)
python tests/manual/test_flagos_dist_live.py --world-size 2Prerequisite: Factory ops honoring their device index is required. Every rank>0 worker builds tensors via a factory, and an output allocated on device 0 while the Triton kernel launches on device N is a cross-device write that faults the GPU. See tests/integration/test_factory_device_index.py.
flagos tensors and boxed kernels' outputs share one pool. dcu_memory.h delegates caching to torch's own allocator through the device-generic registry (c10::getDeviceAllocator(kCUDA)) rather than the c10::cuda:: namespace — the DCU wheel exports c10::hip::HIPCachingAllocator and has zero c10::cuda symbols, and cuda_runtime.h cannot share a translation unit with hip/hip_runtime.h. So memory_allocated() / memory_reserved() / empty_cache() report and act on real usage.
PyTorch's register_privateuse1_backend makes at::getAccelerator() return PrivateUse1, so the autograd engine finds no stream metadata for a pure-CUDA graph and asserts in engine.cpp. This is upstream PrivateUse1 behavior, identical on CUDA/MetaX/Ascend — flagos-device autograd is unaffected. Take .cuda() baselines in a separate process.
Cause: Hygon DMI device management library not found; hyhal driver stack not on LD_LIBRARY_PATH.
Fix: Ensure /usr/local/hyhal/lib (or /opt/hyhal/lib if symlinked) is on LD_LIBRARY_PATH. CI setup prepends it after probing the host driver mount.
Cause: DTK's MIOpen CMake config references the no-longer-present /usr/lib/x86_64-linux-gnu/librt.so.
Fix: This is automatically rewritten by the FLAGOS_ACCELERATOR=dcu branch in CMakeLists.txt. If you see this error, verify FLAGOS_ACCELERATOR=dcu is set and you are using the latest repository code.
Cause: the active triton is not the flagtree build. DTK's own Triton carries
no [triton.backends] entry-point metadata, so entry-point discovery finds
nothing — this is what the deleted TRITON_BACKENDS_IN_TREE=1 workaround used to
paper over by forcing in-tree lookup.
Fix: install the flagtree wheel into the same environment that runs the kernels, after removing any other Triton:
python3 -m pip uninstall -y triton # repeat until fully uninstalled
python3 -m pip install "flagtree===0.6.2a1+hcu3.6" \
--index-url=https://resource.flagos.net/repository/flagos-pypi-hosted/simple
python3 -c 'import triton, triton.backends; print(sorted(triton.backends.backends))'If the venv was created by an older setup, delete site-packages/triton by path
first: pip cannot remove a copy that ships no dist-info, and two Tritons in one
environment resolve by import order.
Cause: hcu Triton backend limitation or MIOpen interaction bug.
Expected behavior. backends_dcu.conf already routes these ops to the cuda boxing kernel. If you see crashes, verify the config is being selected (e.g. FLAGOS_LOG=dispatch and check the [flagos dispatch] lines).
Cause: HSA_FORCE_FINE_GRAIN_PCIE=1 not set, or factory ops not respecting device index.
Fix: Export HSA_FORCE_FINE_GRAIN_PCIE=1 before running multi-card tests. Verify tests/integration/test_factory_device_index.py passes.
Cause: DCU driver not loaded, or /dev/kfd and /dev/dri device nodes not accessible.
Fix: Check vendor-provided driver diagnostics. If running in a container, ensure --privileged or device passthrough is configured.
- DCU without DTK's core libraries — measured symbol attribution, the 32-symbol ABI shim, and the preload order
- Environment Variables — Complete runtime configuration reference
- Profiler Architecture — Device event emission for DCU
- Distributed (FlagCX) — Multi-GPU collectives and RCCL fallback
- Testing Guide — Running local validation equivalent to CI