diff --git a/.github/workflows/build-multiarch.yml b/.github/workflows/build-multiarch.yml new file mode 100644 index 00000000..31471517 --- /dev/null +++ b/.github/workflows/build-multiarch.yml @@ -0,0 +1,120 @@ +name: Build Multi-Architecture Docker Images + +on: + workflow_dispatch: + +jobs: + build: + strategy: + matrix: + include: + - platform: linux/amd64 + runs-on: ubuntu-latest + arch-suffix: amd64 + - platform: linux/arm64 + runs-on: ubuntu-24.04-arm + arch-suffix: arm64 + + runs-on: ${{ matrix.runs-on }} + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Extract version from pyproject.toml + id: version + run: | + VERSION=$(grep '^version = ' pyproject.toml | cut -d '"' -f 2) + echo "version=$VERSION" >> $GITHUB_OUTPUT + echo "Version: $VERSION" + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Login to Docker Hub + if: github.event_name != 'pull_request' + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_PASSWORD }} + + - name: Build and push backend + uses: docker/build-push-action@v5 + with: + context: . + file: ./Dockerfile.backend + platforms: ${{ matrix.platform }} + push: ${{ github.event_name != 'pull_request' }} + tags: phact/openrag-backend:${{ steps.version.outputs.version }}-${{ matrix.arch-suffix }} + cache-from: type=gha + cache-to: type=gha,mode=max + + - name: Build and push frontend + uses: docker/build-push-action@v5 + with: + context: . + file: ./Dockerfile.frontend + platforms: ${{ matrix.platform }} + push: ${{ github.event_name != 'pull_request' }} + tags: phact/openrag-frontend:${{ steps.version.outputs.version }}-${{ matrix.arch-suffix }} + cache-from: type=gha + cache-to: type=gha,mode=max + + - name: Build and push OpenSearch + uses: docker/build-push-action@v5 + with: + context: . + file: ./Dockerfile + platforms: ${{ matrix.platform }} + push: ${{ github.event_name != 'pull_request' }} + tags: phact/openrag-opensearch:${{ steps.version.outputs.version }}-${{ matrix.arch-suffix }} + cache-from: type=gha + cache-to: type=gha,mode=max + + manifest: + needs: build + runs-on: ubuntu-latest + if: github.event_name != 'pull_request' + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Extract version from pyproject.toml + id: version + run: | + VERSION=$(grep '^version = ' pyproject.toml | cut -d '"' -f 2) + echo "version=$VERSION" >> $GITHUB_OUTPUT + + - name: Login to Docker Hub + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_PASSWORD }} + + - name: Create and push multi-arch manifests + run: | + VERSION=${{ steps.version.outputs.version }} + + # Backend manifest + docker buildx imagetools create -t phact/openrag-backend:$VERSION \ + phact/openrag-backend:$VERSION-amd64 \ + phact/openrag-backend:$VERSION-arm64 + docker buildx imagetools create -t phact/openrag-backend:latest \ + phact/openrag-backend:$VERSION-amd64 \ + phact/openrag-backend:$VERSION-arm64 + + # Frontend manifest + docker buildx imagetools create -t phact/openrag-frontend:$VERSION \ + phact/openrag-frontend:$VERSION-amd64 \ + phact/openrag-frontend:$VERSION-arm64 + docker buildx imagetools create -t phact/openrag-frontend:latest \ + phact/openrag-frontend:$VERSION-amd64 \ + phact/openrag-frontend:$VERSION-arm64 + + # OpenSearch manifest + docker buildx imagetools create -t phact/openrag-opensearch:$VERSION \ + phact/openrag-opensearch:$VERSION-amd64 \ + phact/openrag-opensearch:$VERSION-arm64 + docker buildx imagetools create -t phact/openrag-opensearch:latest \ + phact/openrag-opensearch:$VERSION-amd64 \ + phact/openrag-opensearch:$VERSION-arm64 \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index ed7a9aba..77e7651e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -11,12 +11,19 @@ RUN usermod -aG wheel opensearch # Change the sudoers file to allow passwordless sudo RUN echo "opensearch ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers -# FIXME handle the machine arch better, somehow -ARG ASYNC_PROFILER_URL=https://github.com/async-profiler/async-profiler/releases/download/v4.0/async-profiler-4.0-linux-x64.tar.gz +# Handle different architectures for async-profiler +ARG TARGETARCH +RUN if [ "$TARGETARCH" = "amd64" ]; then \ + export ASYNC_PROFILER_URL=https://github.com/async-profiler/async-profiler/releases/download/v4.0/async-profiler-4.0-linux-x64.tar.gz; \ + elif [ "$TARGETARCH" = "arm64" ]; then \ + export ASYNC_PROFILER_URL=https://github.com/async-profiler/async-profiler/releases/download/v4.0/async-profiler-4.0-linux-arm64.tar.gz; \ + else \ + echo "Unsupported architecture: $TARGETARCH" && exit 1; \ + fi && \ + mkdir /opt/async-profiler && \ + curl -s -L $ASYNC_PROFILER_URL | tar zxvf - --strip-components=1 -C /opt/async-profiler && \ + chown -R opensearch:opensearch /opt/async-profiler -RUN mkdir /opt/async-profiler -RUN curl -s -L $ASYNC_PROFILER_URL | tar zxvf - --strip-components=1 -C /opt/async-profiler -RUN chown -R opensearch:opensearch /opt/async-profiler RUN echo "#!/bin/bash" > /usr/share/opensearch/profile.sh RUN echo "export PATH=\$PATH:/opt/async-profiler/bin" >> /usr/share/opensearch/profile.sh diff --git a/Dockerfile.backend b/Dockerfile.backend index f257d4c2..bd88ae1b 100644 --- a/Dockerfile.backend +++ b/Dockerfile.backend @@ -1,7 +1,13 @@ FROM python:3.13-slim # Install curl for uv installation and openssl for RSA key generation -RUN apt-get update && apt-get install -y curl openssl && rm -rf /var/lib/apt/lists/* +# Also install git for potential dependencies and build-essential for native compilations +RUN apt-get update && apt-get install -y \ + curl \ + openssl \ + git \ + build-essential \ + && rm -rf /var/lib/apt/lists/* # Install uv RUN curl -LsSf https://astral.sh/uv/install.sh | sh @@ -15,9 +21,24 @@ COPY pyproject.toml uv.lock ./ RUN uv sync # Copy sample document and warmup script for docling -COPY documents/2506.08231v1.pdf ./ +COPY documents/warmup_ocr.pdf ./ COPY warm_up_docling.py ./ -RUN uv run python warm_up_docling.py && rm warm_up_docling.py 2506.08231v1.pdf +RUN uv run docling-tools models download +RUN uv run python - <<'PY' +import pathlib, easyocr +cache = pathlib.Path("/root/.EasyOCR/model") +cache.mkdir(parents=True, exist_ok=True) +# Prewarm the detector + recog for Docling’s default langs +easyocr.Reader(['fr','de','es','en'], + download_enabled=True, + model_storage_directory=str(cache)) +print("EasyOCR cache ready at", cache) +PY + +RUN uv run python warm_up_docling.py && rm warm_up_docling.py warmup_ocr.pdf + + +#ENV EASYOCR_MODULE_PATH=~/.cache/docling/models/EasyOcr/ # Copy Python source COPY src/ ./src/ @@ -26,4 +47,4 @@ COPY src/ ./src/ EXPOSE 8000 # Start backend in foreground -CMD ["uv", "run", "python", "src/main.py"] \ No newline at end of file +CMD ["uv", "run", "python", "src/main.py"] diff --git a/docker-compose.yml b/docker-compose.yml index 342cd4ba..ebb6e691 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -67,7 +67,6 @@ services: - ./documents:/app/documents:Z - ./keys:/app/keys:Z gpus: all - platform: linux/amd64 openrag-frontend: image: phact/openrag-frontend:latest diff --git a/documents/warmup_ocr.pdf b/documents/warmup_ocr.pdf new file mode 100644 index 00000000..8b17f8b2 Binary files /dev/null and b/documents/warmup_ocr.pdf differ diff --git a/pyproject.toml b/pyproject.toml index 89b70864..d46ca3eb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -27,10 +27,11 @@ dependencies = [ [tool.uv.sources] #agentd = { path = "/home/tato/Desktop/agentd" } torch = [ - { index = "pytorch-cu128" }, + { index = "pytorch-cu128", marker = "sys_platform == 'linux' and platform_machine == 'x86_64'" }, + # macOS & other platforms use PyPI (no index entry needed) ] torchvision = [ - { index = "pytorch-cu128" }, + { index = "pytorch-cu128", marker = "sys_platform == 'linux' and platform_machine == 'x86_64'" }, ] [[tool.uv.index]] diff --git a/warm_up_docling.py b/warm_up_docling.py index 30c7489f..50fe7c6b 100644 --- a/warm_up_docling.py +++ b/warm_up_docling.py @@ -4,10 +4,10 @@ print('Warming up docling models...') try: # Use the sample document to warm up docling - test_file = "/app/2506.08231v1.pdf" + test_file = "/app/warmup_ocr.pdf" print(f'Using {test_file} to warm up docling...') DocumentConverter().convert(test_file) print('Docling models warmed up successfully') except Exception as e: print(f'Docling warm-up completed with: {e}') - # This is expected - we just want to trigger the model downloads \ No newline at end of file + # This is expected - we just want to trigger the model downloads