mirror of
https://github.com/radixark/miles.git
synced 2026-10-02 07:14:53 +08:00
[AMD] Merge MI300/MI350-5 Dockerfiles (#1294)
Co-authored-by: Xinyu Jiang <xinyuj2@andrew.cmu.edu>
This commit is contained in:
co-authored by
Xinyu Jiang
parent
04b61f31a1
commit
c5401d646a
@@ -1,13 +1,18 @@
|
||||
# 1. rlsys/miles:MI350-355-latest
|
||||
# build-arg:SGLANG_IMAGE_TAG=v0.5.10-rocm720-mi35x
|
||||
# Multi-arch ROCm image for Miles (built on a prebuilt sglang base):
|
||||
#
|
||||
# 1. rocm/sgl-dev:MI350-355-latest (gfx950, ROCm 7.2 / MI350 / MI355X)
|
||||
# use default settings
|
||||
#
|
||||
# 2. rocm/sgl-dev:MI300-latest (gfx942, ROCm 7.0 / MI300 / MI325)
|
||||
# build-arg:GPU_ARCH=gfx942
|
||||
# build-arg:SGLANG_IMAGE_TAG=v0.5.10-rocm700-mi30x
|
||||
|
||||
ARG SGLANG_IMAGE_TAG=v0.5.10-rocm720-mi35x
|
||||
FROM lmsysorg/sglang:${SGLANG_IMAGE_TAG} AS sglang
|
||||
|
||||
SHELL ["/bin/bash", "-ceuxo", "pipefail"]
|
||||
|
||||
# ======================================== Arguments =============================================
|
||||
|
||||
# ======================================== Arguments ============================================
|
||||
ARG SGLANG_BRANCH=sglang-miles
|
||||
ARG SGLANG_COMMIT=""
|
||||
|
||||
@@ -29,13 +34,12 @@ ARG RCCL_TESTS_PATH=projects/rccl-tests
|
||||
ARG TRANSFORMER_ENGINE_REPO=https://github.com/ROCm/TransformerEngine.git
|
||||
ARG TRANSFORMER_ENGINE_BRANCH=v2.8_rocm
|
||||
|
||||
# ======================================== Setup =============================================
|
||||
|
||||
# ======================================== Setup ================================================
|
||||
WORKDIR /root/
|
||||
|
||||
ENV MAX_JOBS=${MAX_JOBS}
|
||||
|
||||
# Build configuration for MI350 / gfx950.
|
||||
# Build configuration for the selected arch (gfx942 / gfx950).
|
||||
ENV GPU_ARCH=${GPU_ARCH}
|
||||
ENV PYTORCH_ROCM_ARCH=${GPU_ARCH}
|
||||
ENV GPU_ARCH_LIST=${GPU_ARCH}
|
||||
@@ -46,18 +50,16 @@ ENV NVTE_FRAMEWORK=pytorch
|
||||
ENV NVTE_ROCM_ARCH=${GPU_ARCH}
|
||||
ENV NVTE_USE_HIPBLASLT=1
|
||||
ENV NVTE_USE_ROCM=1
|
||||
# Keep the core package enabled and skip the extra fused-attn kernel matrix rebuild.
|
||||
ENV NVTE_FUSED_ATTN=0
|
||||
ENV CMAKE_PREFIX_PATH=/opt/rocm:/opt/rocm/hip:/usr/local:/usr
|
||||
|
||||
# Patch Megatron's fused-kernel init for this toolchain.
|
||||
# Patches + requirements consumed below.
|
||||
# TODO: remove these patches once the changes are merged into the main codebase.
|
||||
COPY docker/amd_patch/latest/megatron.patch /tmp/amd_patch/megatron.patch
|
||||
COPY docker/amd_patch/latest/miles.patch /tmp/amd_patch/miles.patch
|
||||
COPY docker/amd_patch/latest/sglang_attn_bridge /tmp/amd_patch/sglang_attn_bridge
|
||||
COPY requirements.txt /tmp/requirements.txt
|
||||
|
||||
# ======================================== Apt dependencies =============================================
|
||||
|
||||
# ======================================== Apt dependencies =====================================
|
||||
RUN apt update
|
||||
# Install build tools and diagnostics utilities.
|
||||
RUN apt install -y build-essential cmake dnsutils ethtool git nvtop rsync
|
||||
@@ -71,30 +73,14 @@ RUN git clone --depth 1 --branch ${RCCL_TESTS_BRANCH} ${RCCL_TESTS_REPO} /tmp/ro
|
||||
cp /tmp/rocm-systems/${RCCL_TESTS_PATH}/build/*_perf /usr/local/bin/ && \
|
||||
rm -rf /tmp/rocm-systems
|
||||
|
||||
# ====================================== Python dependencies ============================================
|
||||
|
||||
# Rebuild AITER at the version paired with SGLang.
|
||||
RUN pip uninstall -y aiter || true
|
||||
RUN pip install flydsl==0.0.1.dev95158637 psutil pybind11
|
||||
RUN cd /sgl-workspace/aiter && \
|
||||
git remote set-url origin ${AITER_REPO} && \
|
||||
git checkout ${AITER_COMMIT} && \
|
||||
git reset --hard ${AITER_COMMIT} && \
|
||||
git clean -fdx && \
|
||||
git submodule sync --recursive && \
|
||||
git submodule update --init --recursive && \
|
||||
# Temporary fixes for the current ROCm 7.2 image/toolchain combination.
|
||||
sed -i '459 s/if.*:/if False:/' aiter/ops/triton/attention/pa_mqa_logits.py && \
|
||||
sed -i '/c1 = torch.empty((M, D, S1 + S3), dtype=dtype, device=x.device)/i\ config = dict(config)' \
|
||||
aiter/ops/triton/gemm/fused/fused_gemm_afp4wfp4_split_cat.py && \
|
||||
GPU_ARCHS=${GPU_ARCH} pip install -e .
|
||||
|
||||
# Install Transformer Engine from the requested branch.
|
||||
# ====================================== Python dependencies ====================================
|
||||
# Install Transformer Engine from the requested branch. NVTE_FUSED_ATTN=0 is scoped to
|
||||
# this build step only (skip the extra fused-attn kernel matrix rebuild), not the runtime env.
|
||||
RUN pip uninstall -y transformer-engine transformer_engine transformer_engine_torch || true
|
||||
RUN rm -rf /root/TransformerEngine && \
|
||||
git clone --recursive --branch ${TRANSFORMER_ENGINE_BRANCH} ${TRANSFORMER_ENGINE_REPO} /root/TransformerEngine && \
|
||||
cd /root/TransformerEngine && \
|
||||
pip install . --no-build-isolation -v
|
||||
NVTE_FUSED_ATTN=0 pip install . --no-build-isolation -v
|
||||
|
||||
RUN pip install git+https://github.com/ISEEKYAN/mbridge.git@89eb10887887bc74853f89a4de258c0702932a1c --no-deps
|
||||
|
||||
@@ -137,23 +123,41 @@ RUN pip install git+https://github.com/yushengsu-thu/Megatron-Bridge.git@merged-
|
||||
RUN pip install megatron-energon --no-deps
|
||||
RUN pip install multi-storage-client --no-deps
|
||||
|
||||
# Megatron post_training and Megatron-Bridge hard-import modelopt.
|
||||
RUN pip install "nvidia-modelopt[torch]>=0.37.0" --no-build-isolation
|
||||
# modelopt pulls setuptools up to 82, which drops pkg_resources and breaks deps that
|
||||
# still import it; re-pin to the base image's setuptools.
|
||||
RUN pip install "setuptools==79.0.1"
|
||||
|
||||
# Pin the CLI stack (typer/click) for huggingface_hub / Ray compatibility.
|
||||
RUN pip install "typer==0.25.1" "click==8.2.1"
|
||||
|
||||
RUN rm -rf /usr/lib/python3/dist-packages/jwt /usr/lib/python3/dist-packages/PyJWT* && \
|
||||
pip install -r /tmp/requirements.txt
|
||||
|
||||
# Pin numpy 1.x for Megatron compatibility.
|
||||
RUN pip install "numpy<2"
|
||||
|
||||
# ====================================== Install main package ============================================
|
||||
# ====================================== Install main package ===================================
|
||||
|
||||
RUN git clone https://github.com/radixark/miles.git /root/miles && \
|
||||
cd /root/miles && \
|
||||
git checkout ${MILES_COMMIT} && \
|
||||
git apply --3way --unidiff-zero /tmp/amd_patch/miles.patch && \
|
||||
git apply --3way --unidiff-zero /tmp/amd_patch/miles.patch && \
|
||||
mkdir -p miles/backends/experimental/fsdp_utils/sglang_attn_bridge && \
|
||||
cp -r /tmp/amd_patch/sglang_attn_bridge/. miles/backends/experimental/fsdp_utils/sglang_attn_bridge/ && \
|
||||
pip install -e . --no-deps
|
||||
|
||||
# ====================================== Runtime knobs ============================================
|
||||
# 3.10 compatibility shim: miles uses the Python 3.11 StrEnum, but the ROCm sglang base is py3.10
|
||||
# (the NV/CUDA base is already py3.12). Backport StrEnum on <3.11 and make the imports
|
||||
# version-guarded. TODO: remove once the ROCm base is py3.11+.
|
||||
RUN pip install backports.strenum && \
|
||||
for f in $(grep -rlE '^from enum import .*StrEnum' /root/miles/miles || true); do \
|
||||
sed -i -E 's/^from enum import Enum, StrEnum$/from enum import Enum\ntry:\n from enum import StrEnum\nexcept ImportError:\n from backports.strenum import StrEnum/' "$f" && \
|
||||
sed -i -E 's/^from enum import StrEnum$/try:\n from enum import StrEnum\nexcept ImportError:\n from backports.strenum import StrEnum/' "$f"; \
|
||||
done
|
||||
|
||||
# ====================================== Runtime knobs ==========================================
|
||||
|
||||
# Runtime knobs consumed by the current SGLang/PyTorch stack.
|
||||
ENV HIP_FORCE_DEV_KERNARG=1
|
||||
@@ -1,286 +0,0 @@
|
||||
#### Use the base image for ROCm 7 / gfx942 (MI300)
|
||||
|
||||
# =====================================================================
|
||||
# Docker Image Version Information (Updated: Feb 5, 2026)
|
||||
# =====================================================================
|
||||
# Base image: ROCm 7 with vllm pre-built for gfx942
|
||||
# Target GPU: MI300 (gfx942)
|
||||
#
|
||||
# Key Dependencies:
|
||||
# - sglang: sglang-miles branch
|
||||
# - sgl_kernel: built from selected sglang commit
|
||||
# - Megatron-LM: radixark/Megatron-LM
|
||||
# - TransformerEngine: commit 90c04bcdc3c109505b318f40a39680263af55edf
|
||||
# - aiter: v0.1.10.post3
|
||||
# - Ray: 2.47.1
|
||||
#
|
||||
# Patches: amd_patch/sglv0.5.7/
|
||||
# - megatron.patch
|
||||
# - sglang.patch
|
||||
# =====================================================================
|
||||
|
||||
|
||||
FROM rocm/sgl-dev:rocm7-vllm-20250904
|
||||
|
||||
SHELL ["/bin/bash", "-ceuxo", "pipefail"]
|
||||
|
||||
ARG MAX_JOBS=128
|
||||
ARG SGLANG_REPO=sgl-project/sglang
|
||||
ARG SGLANG_BRANCH=sglang-miles
|
||||
ARG SGLANG_COMMIT=""
|
||||
ARG MEGATRON_REPO=radixark/Megatron-LM
|
||||
ARG MEGATRON_BRANCH=miles-main
|
||||
ARG MEGATRON_COMMIT=""
|
||||
ENV MAX_JOBS=${MAX_JOBS}
|
||||
|
||||
# Set environment variables for gfx942
|
||||
ENV GPU_ARCH=gfx942
|
||||
ENV PYTORCH_ROCM_ARCH=gfx942
|
||||
ENV GPU_ARCH_LIST=gfx942
|
||||
ENV AMDGPU_TARGET=gfx942
|
||||
|
||||
|
||||
###########################################
|
||||
##############1. Install AITER#############
|
||||
###########################################
|
||||
WORKDIR /app
|
||||
|
||||
RUN pip uninstall -y aiter || true
|
||||
RUN rm -rf aiter
|
||||
RUN git clone https://github.com/ROCm/aiter.git \
|
||||
&& cd aiter \
|
||||
&& git checkout v0.1.10.post3 \
|
||||
&& curl -fsSL https://patch-diff.githubusercontent.com/raw/ROCm/aiter/pull/2075.patch -o /tmp/aiter-pr2075.patch \
|
||||
&& git apply --3way /tmp/aiter-pr2075.patch \
|
||||
&& rm -f /tmp/aiter-pr2075.patch \
|
||||
&& git submodule sync --recursive \
|
||||
&& git submodule update --init --recursive \
|
||||
&& GPU_ARCHS=gfx942 python setup.py develop
|
||||
###########################################
|
||||
###########################################
|
||||
###########################################
|
||||
|
||||
|
||||
###########################################
|
||||
####2. Install TransformerEngine for gfx942
|
||||
###########################################
|
||||
WORKDIR /app
|
||||
|
||||
RUN rm -rf TransformerEngine
|
||||
RUN git clone https://github.com/ROCm/TransformerEngine.git \
|
||||
&& cd TransformerEngine \
|
||||
&& git checkout 90c04bcdc3c109505b318f40a39680263af55edf \
|
||||
&& git submodule update --init --recursive
|
||||
|
||||
ENV NVTE_FRAMEWORK=pytorch
|
||||
ENV NVTE_ROCM_ARCH=gfx942
|
||||
ENV NVTE_USE_HIPBLASLT=1
|
||||
ENV NVTE_USE_ROCM=1
|
||||
ENV CMAKE_PREFIX_PATH="/opt/rocm:/opt/rocm/hip:/usr/local:/usr"
|
||||
|
||||
RUN cd TransformerEngine && pip install . -v
|
||||
###########################################
|
||||
###########################################
|
||||
###########################################
|
||||
|
||||
|
||||
#########################################
|
||||
####3. Install Megatron-LM
|
||||
#########################################
|
||||
WORKDIR /app
|
||||
|
||||
RUN pip install "numpy>=1.21.0,<2.0" --force-reinstall
|
||||
|
||||
RUN pip uninstall -y megatron-core || true
|
||||
RUN rm -rf Megatron-LM
|
||||
RUN git clone https://github.com/${MEGATRON_REPO}.git \
|
||||
&& cd Megatron-LM \
|
||||
&& git fetch origin ${MEGATRON_BRANCH} \
|
||||
&& if [ -n "${MEGATRON_COMMIT}" ]; then \
|
||||
git checkout ${MEGATRON_COMMIT}; \
|
||||
else \
|
||||
git checkout FETCH_HEAD; \
|
||||
fi \
|
||||
&& pip install -e .
|
||||
#########################################
|
||||
#########################################
|
||||
#########################################
|
||||
|
||||
|
||||
########################################
|
||||
############ 4. Install mbridge#########
|
||||
########################################
|
||||
RUN pip install git+https://github.com/ISEEKYAN/mbridge.git --no-deps
|
||||
########################################
|
||||
########################################
|
||||
########################################
|
||||
|
||||
|
||||
########################################
|
||||
######5. Install Ray####################
|
||||
########################################
|
||||
RUN pip uninstall ray -y || true
|
||||
RUN pip install "ray[data,train,tune,serve]==2.47.1"
|
||||
########################################
|
||||
########################################
|
||||
########################################
|
||||
|
||||
|
||||
#########################################
|
||||
###6. Install torch_memory_saver#########
|
||||
#########################################
|
||||
RUN pip install git+https://github.com/fzyzcjy/torch_memory_saver.git@64a92e1d7fb822ea4af5579c8cebb162692c531c --no-cache-dir --force-reinstall
|
||||
#########################################
|
||||
#########################################
|
||||
|
||||
|
||||
#######################################
|
||||
####7. Install Apex for ROCm###########
|
||||
#######################################
|
||||
WORKDIR /app
|
||||
|
||||
RUN pip uninstall -y apex || true
|
||||
RUN rm -rf apex
|
||||
RUN git clone https://github.com/ROCm/apex.git \
|
||||
&& cd apex \
|
||||
&& python setup.py install
|
||||
#######################################
|
||||
#######################################
|
||||
#######################################
|
||||
|
||||
|
||||
########################################
|
||||
###8. Install miles agent framework deps
|
||||
########################################
|
||||
RUN pip install pydra_config==0.0.15
|
||||
RUN pip install together
|
||||
RUN pip install google-generativeai
|
||||
RUN pip install tensorboard
|
||||
########################################
|
||||
########################################
|
||||
########################################
|
||||
|
||||
|
||||
########################################
|
||||
###9. Set performance environment vars##
|
||||
########################################
|
||||
ENV HIP_FORCE_DEV_KERNARG=1
|
||||
ENV HSA_NO_SCRATCH_RECLAIM=1
|
||||
ENV SGLANG_USE_AITER=1
|
||||
ENV SGLANG_ALLOW_OVERWRITE_LONGER_CONTEXT_LEN=1
|
||||
ENV SGLANG_MOE_PADDING=1
|
||||
ENV SGLANG_SET_CPU_AFFINITY=1
|
||||
ENV SGLANG_ROCM_FUSED_DECODE_MLA=1
|
||||
ENV SGLANG_USE_ROCM700A=1
|
||||
ENV NCCL_MIN_NCHANNELS=112
|
||||
ENV VLLM_FP8_PADDING=1
|
||||
ENV VLLM_FP8_ACT_PADDING=1
|
||||
ENV VLLM_FP8_WEIGHT_PADDING=1
|
||||
ENV VLLM_FP8_REDUCE_CONV=1
|
||||
ENV TORCHINDUCTOR_MAX_AUTOTUNE=1
|
||||
ENV TORCHINDUCTOR_MAX_AUTOTUNE_POINTWISE=1
|
||||
########################################
|
||||
########################################
|
||||
########################################
|
||||
|
||||
|
||||
###########################################
|
||||
##############Install SGLang###############
|
||||
###########################################
|
||||
WORKDIR /app
|
||||
|
||||
# Install prerequisites
|
||||
RUN pip install IPython orjson python-multipart torchao==0.9.0 pybind11
|
||||
|
||||
# Clone SGLang
|
||||
RUN pip uninstall -y sgl_kernel sglang || true
|
||||
RUN rm -rf sglang
|
||||
RUN git clone https://github.com/${SGLANG_REPO}.git \
|
||||
&& cd sglang \
|
||||
&& git fetch origin ${SGLANG_BRANCH} \
|
||||
&& if [ -n "${SGLANG_COMMIT}" ]; then \
|
||||
git checkout ${SGLANG_COMMIT}; \
|
||||
else \
|
||||
git checkout FETCH_HEAD; \
|
||||
fi
|
||||
|
||||
# Build sgl-kernel for gfx942
|
||||
RUN cd sglang/sgl-kernel \
|
||||
&& rm -f pyproject.toml \
|
||||
&& mv pyproject_rocm.toml pyproject.toml \
|
||||
&& AMDGPU_TARGET=gfx942 python setup_rocm.py install
|
||||
|
||||
# Install SGLang
|
||||
RUN cd sglang \
|
||||
&& rm -rf python/pyproject.toml \
|
||||
&& mv python/pyproject_other.toml python/pyproject.toml \
|
||||
&& pip install -e "python[all_hip]"
|
||||
|
||||
# Test SGLang installation
|
||||
RUN python -c "import sglang; import sgl_kernel; print('SGLang + sgl_kernel: OK')"
|
||||
|
||||
RUN python -m pip cache purge
|
||||
###########################################
|
||||
###########################################
|
||||
###########################################
|
||||
|
||||
|
||||
###########################################
|
||||
#### APPLY PATCHES (gfx942/MI300) #########
|
||||
###########################################
|
||||
|
||||
# Copy patch from miles repo
|
||||
COPY amd_patch/sglv0.5.7/megatron.patch /app/patch/megatron.patch
|
||||
COPY amd_patch/sglv0.5.7/sglang.patch /app/patch/sglang.patch
|
||||
|
||||
# Apply Megatron patches
|
||||
RUN cd /app/Megatron-LM \
|
||||
&& git apply --3way /app/patch/megatron.patch \
|
||||
&& if grep -R -n '^<<<<<<< ' .; then \
|
||||
echo "Patch failed to apply cleanly. Please resolve conflicts." && \
|
||||
exit 1; \
|
||||
fi \
|
||||
&& pip install -e . -v
|
||||
|
||||
# Apply SGLang patch
|
||||
RUN cd /app/sglang \
|
||||
&& git apply --3way /app/patch/sglang.patch \
|
||||
&& if grep -R -n '^<<<<<<< ' .; then \
|
||||
echo "SGLang patch failed to apply cleanly. Please resolve conflicts." && \
|
||||
exit 1; \
|
||||
fi
|
||||
|
||||
# Copy MOE configs for gfx942/MI300
|
||||
RUN find /app/sglang/python/sglang/srt/layers/quantization/configs/ \
|
||||
/app/sglang/python/sglang/srt/layers/moe/fused_moe_triton/configs/ \
|
||||
-type f -name '*MI300X*' 2>/dev/null | while read f; do \
|
||||
cp "$f" "$(echo $f | sed 's/MI300X/MI300X_VF/')" 2>/dev/null || true; \
|
||||
cp "$f" "$(echo $f | sed 's/MI300X/MI355/')" 2>/dev/null || true; \
|
||||
done
|
||||
|
||||
###########################################
|
||||
###########################################
|
||||
###########################################
|
||||
|
||||
|
||||
########################################
|
||||
#### Install additional packages########
|
||||
########################################
|
||||
RUN pip install sglang-router --force-reinstall
|
||||
########################################
|
||||
########################################
|
||||
########################################
|
||||
|
||||
|
||||
########################################
|
||||
# Fix click/ray incompatibility with Python 3.10
|
||||
########################################
|
||||
RUN pip install click==8.2.1
|
||||
########################################
|
||||
########################################
|
||||
########################################
|
||||
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
CMD ["/usr/bin/bash"]
|
||||
+26
-1
@@ -47,6 +47,26 @@ VARIANTS = {
|
||||
"tag_postfix": "",
|
||||
"build_args": {},
|
||||
},
|
||||
"rocm-mi350": {
|
||||
"image": "rocm/sgl-dev",
|
||||
"tag_postfix": "-rocm720-mi35x",
|
||||
"tag_prefix": "miles",
|
||||
"dockerfile": "docker/Dockerfile.rocm",
|
||||
"build_args": {
|
||||
"GPU_ARCH": "gfx950",
|
||||
"SGLANG_IMAGE_TAG": "v0.5.10-rocm720-mi35x",
|
||||
},
|
||||
},
|
||||
"rocm-mi300": {
|
||||
"image": "rocm/sgl-dev",
|
||||
"tag_postfix": "-rocm700-mi30x",
|
||||
"tag_prefix": "miles",
|
||||
"dockerfile": "docker/Dockerfile.rocm",
|
||||
"build_args": {
|
||||
"GPU_ARCH": "gfx942",
|
||||
"SGLANG_IMAGE_TAG": "v0.5.10-rocm700-mi30x",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -61,14 +81,17 @@ def build_and_push(
|
||||
variant: str, image_tag: str, dry_run: bool, dockerfile: str, push: bool = False, custom_tag: str = ""
|
||||
) -> None:
|
||||
config = VARIANTS[variant]
|
||||
# A variant may pin its own Dockerfile (e.g. ROCm); otherwise use the CLI default.
|
||||
dockerfile = config.get("dockerfile", dockerfile)
|
||||
image = config["image"]
|
||||
postfix = config.get("tag_postfix", "")
|
||||
|
||||
if image_tag == "latest":
|
||||
tags = [f"{image}:latest{postfix}"]
|
||||
elif image_tag == "dev":
|
||||
prefix = config.get("tag_prefix", "dev")
|
||||
timestamp = datetime.now(timezone.utc).strftime("%Y%m%d%H%M")
|
||||
tags = [f"{image}:dev{postfix}", f"{image}:dev{postfix}-{timestamp}"]
|
||||
tags = [f"{image}:{prefix}{postfix}", f"{image}:{prefix}{postfix}-{timestamp}"]
|
||||
elif image_tag == "custom":
|
||||
if not custom_tag:
|
||||
raise typer.BadParameter("--custom-tag is required when --image-tag is custom")
|
||||
@@ -114,6 +137,8 @@ class Variant(str, Enum):
|
||||
cu129_arm64 = "cu129-arm64"
|
||||
cu13_arm64 = "cu13-arm64"
|
||||
debug = "debug"
|
||||
rocm_mi350 = "rocm-mi350"
|
||||
rocm_mi300 = "rocm-mi300"
|
||||
|
||||
|
||||
class ImageTag(str, Enum):
|
||||
|
||||
Reference in New Issue
Block a user