File size: 8,119 Bytes
e479c46 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 | #!/bin/bash
# Build missing aarch64 dGPU wheels before the root pyproject's path sources are
# resolved by uv sync. Dependency pins are the source of truth; the pyproject
# path sources are updated to point at the matching generated wheel names.
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_ROOT="$(cd "$SCRIPT_DIR/../../.." && pwd)"
PYPROJECT="$REPO_ROOT/pyproject.toml"
WHEEL_DIR="$SCRIPT_DIR/wheels"
mkdir -p "$WHEEL_DIR"
# Target CPython derived from the project's requires-python, so the build venv
# and the wheel's cpXY tag track the pinned interpreter. A literal cp310/python3.10
# silently builds an unusable wheel after a runtime bump (e.g. the py3.12 migration).
resolve_python_version() {
python3 - "$PYPROJECT" <<'PY'
import pathlib
import re
import sys
text = pathlib.Path(sys.argv[1]).read_text()
# Accept both TOML quote styles (" and ', written as hex \x22 / \x27).
m = re.search(r"requires-python\s*=\s*[\x22\x27]([^\x22\x27]+)[\x22\x27]", text)
if m is None:
raise SystemExit("ERROR: requires-python not found in pyproject.toml")
v = re.search(r"(\d+)\.(\d+)", m.group(1))
if v is None:
raise SystemExit(f"ERROR: could not parse a CPython version from {m.group(1)!r}")
print(f"{v.group(1)}.{v.group(2)}")
PY
}
PY_VERSION="$(resolve_python_version)"
PYTHON_BIN="python${PY_VERSION}"
CP_TAG="cp${PY_VERSION/./}"
resolve_dependency_version() {
python3 - "$PYPROJECT" "$1" <<'PY'
import pathlib
import re
import sys
pyproject_path = pathlib.Path(sys.argv[1])
package = sys.argv[2]
text = pyproject_path.read_text()
dep_line = None
for line in text.splitlines():
if f'"{package}==' in line and "platform_machine" in line and "aarch64" in line:
dep_line = line
break
if dep_line is None:
for line in text.splitlines():
if f'"{package}==' in line:
dep_line = line
break
if dep_line is None:
raise SystemExit(f"ERROR: pinned project dependency not found for {package}")
dep_match = re.search(rf'"{re.escape(package)}==([^";]+)', dep_line)
if dep_match is None:
raise SystemExit(f"ERROR: could not parse pinned dependency version for {package}")
print(dep_match.group(1))
PY
}
update_pyproject_path_source() {
python3 - "$PYPROJECT" "$1" "$2" <<'PY'
import pathlib
import re
import sys
pyproject_path = pathlib.Path(sys.argv[1])
package = sys.argv[2]
expected_path = sys.argv[3]
text = pyproject_path.read_text()
source_block_match = re.search(
rf"^{re.escape(package)}\s*=\s*\[(.*?)^\]",
text,
flags=re.MULTILINE | re.DOTALL,
)
if source_block_match is None:
raise SystemExit(f"ERROR: [tool.uv.sources] entry not found for {package}")
block_start, block_end = source_block_match.span(1)
block = source_block_match.group(1)
entry_pattern = re.compile(r"\{[^\n]*path\s*=\s*\"([^\"]+)\"[^\n]*\}")
matches = list(entry_pattern.finditer(block))
aarch64_matches = [
match
for match in matches
if "platform_machine == 'aarch64'" in match.group(0)
and "scripts/deployment/dgpu/wheels/" in match.group(1)
]
if len(aarch64_matches) != 1:
raise SystemExit(
f"ERROR: expected exactly one dGPU aarch64 path source for {package}, "
f"found {len(aarch64_matches)}"
)
match = aarch64_matches[0]
current_path = match.group(1)
if current_path == expected_path:
print(f"{package} path source already points to {expected_path}")
raise SystemExit(0)
entry = match.group(0)
updated_entry = entry.replace(f'path = "{current_path}"', f'path = "{expected_path}"')
updated_block = block[: match.start()] + updated_entry + block[match.end() :]
updated_text = text[:block_start] + updated_block + text[block_end:]
pyproject_path.write_text(updated_text)
print(f"Updated {package} path source: {current_path} -> {expected_path}")
PY
}
wheel_path_for() {
package_prefix="$1"
version="$2"
printf "%s/%s-%s-${CP_TAG}-${CP_TAG}-linux_aarch64.whl" "$WHEEL_DIR" "$package_prefix" "$version"
}
# flash-attn now ships as official cu12torch2.9 cp312 release wheels for both
# x86_64 and aarch64 (see [tool.uv.sources] in pyproject.toml), so the dGPU
# bootstrap only owns the aarch64 torchcodec wheel (no PyPI aarch64 wheel).
TORCHCODEC_VERSION="$(resolve_dependency_version "torchcodec")"
TORCHCODEC_SOURCE_VERSION="$(printf "%s" "$TORCHCODEC_VERSION" | sed -E 's/a[0-9]+$//')"
TORCHCODEC_WHEEL="$(wheel_path_for "torchcodec" "$TORCHCODEC_VERSION")"
TORCHCODEC_PATH="scripts/deployment/dgpu/wheels/$(basename "$TORCHCODEC_WHEEL")"
echo "Expected dGPU aarch64 wheels from dependency pins:"
echo " torchcodec==$TORCHCODEC_VERSION -> $TORCHCODEC_PATH"
update_pyproject_path_source "torchcodec" "$TORCHCODEC_PATH"
if [ "${DGPU_WHEEL_BOOTSTRAP_VALIDATE_ONLY:-0}" = "1" ]; then
exit 0
fi
if [ "$(uname -m)" != "aarch64" ]; then
echo "dGPU wheel bootstrap is only needed on aarch64; skipping."
exit 0
fi
if [ -f "$TORCHCODEC_WHEEL" ]; then
echo "Matching dGPU aarch64 torchcodec wheel already exists; skipping source build."
exit 0
fi
if ! command -v uv &> /dev/null; then
echo "ERROR: uv is required to bootstrap dGPU wheels." >&2
exit 1
fi
BUILD_VENV="${DGPU_WHEEL_BUILD_VENV:-/tmp/gr00t-dgpu-wheel-build-venv}"
BUILD_PYTHON="$BUILD_VENV/bin/python"
TMP_BUILD_DIRS=()
trap 'rm -rf "$BUILD_VENV"; for _d in "${TMP_BUILD_DIRS[@]:-}"; do rm -rf "$_d"; done' EXIT
rm -rf "$BUILD_VENV"
"$PYTHON_BIN" -m venv "$BUILD_VENV"
uv_pip_install_retry() {
for attempt in 1 2 3 4 5; do
if uv pip install "$@"; then
return 0
fi
sleep $((attempt * 10))
done
return 1
}
# Build against the project's pinned torch / triton / numpy (derived from
# pyproject.toml) so the generated wheel's ABI matches the runtime stack.
# Hardcoded versions silently produce a wheel for the wrong torch after a bump.
TORCH_VERSION="$(resolve_dependency_version "torch")"
TRITON_VERSION="$(resolve_dependency_version "triton")"
NUMPY_VERSION="$(resolve_dependency_version "numpy")"
uv_pip_install_retry --python "$BUILD_PYTHON" \
--index-url https://download.pytorch.org/whl/cu128 \
--extra-index-url https://pypi.org/simple \
"torch==${TORCH_VERSION}" "triton==${TRITON_VERSION}" "numpy==${NUMPY_VERSION}"
uv_pip_install_retry --python "$BUILD_PYTHON" pip setuptools wheel packaging ninja
SITE_PKGS=$("$BUILD_PYTHON" - <<'PY'
import site
print(site.getsitepackages()[0])
PY
)
NVIDIA_LIB_DIRS="$(find "${SITE_PKGS}/nvidia" -name "lib" -type d 2>/dev/null | tr '\n' ':')"
export LD_LIBRARY_PATH="/usr/local/cuda/lib64:${SITE_PKGS}/torch/lib:${NVIDIA_LIB_DIRS}${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}"
export CUDA_HOME=/usr/local/cuda
export CUDA_PATH=/usr/local/cuda
export CPATH="${CUDA_HOME}/include:${CPATH:-}"
export C_INCLUDE_PATH="${CUDA_HOME}/include:${C_INCLUDE_PATH:-}"
export CPLUS_INCLUDE_PATH="${CUDA_HOME}/include:${CPLUS_INCLUDE_PATH:-}"
export MAX_JOBS="${MAX_JOBS:-$(nproc)}"
export NVCC_THREADS="${NVCC_THREADS:-1}"
export CMAKE_BUILD_PARALLEL_LEVEL="${CMAKE_BUILD_PARALLEL_LEVEL:-$(nproc)}"
build_torchcodec() {
if [ -f "$TORCHCODEC_WHEEL" ]; then
echo "torchcodec wheel already exists: $TORCHCODEC_WHEEL"
return
fi
echo "No dGPU aarch64 torchcodec wheel found; building from source..."
rm -rf /tmp/torchcodec
TMP_BUILD_DIRS+=(/tmp/torchcodec)
git clone --depth 1 --branch "v${TORCHCODEC_SOURCE_VERSION}" \
https://github.com/pytorch/torchcodec.git /tmp/torchcodec
rm -rf /tmp/torchcodec/.git
I_CONFIRM_THIS_IS_NOT_A_LICENSE_VIOLATION=1 "$BUILD_PYTHON" -m pip wheel \
--no-build-isolation \
--no-deps \
--wheel-dir "$WHEEL_DIR" \
/tmp/torchcodec
if [ ! -f "$TORCHCODEC_WHEEL" ]; then
echo "ERROR: torchcodec source build did not produce expected wheel:" >&2
echo " $TORCHCODEC_WHEEL" >&2
echo "Available torchcodec wheels:" >&2
find "$WHEEL_DIR" -maxdepth 1 -name 'torchcodec-*.whl' -print >&2
exit 1
fi
}
cd "$REPO_ROOT"
build_torchcodec
echo "dGPU aarch64 wheel bootstrap complete:"
ls -lh "$TORCHCODEC_WHEEL"
|