diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 000000000..a73aea982 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,32 @@ +# Local venv and Python caches — uv rebuilds inside the image. +.venv/ +**/__pycache__/ +**/*.pyc + +# Build artefacts. +**/build/ +**/build-*/ +dflash/build/ + +# Model weights — bind-mount at runtime instead of baking into the image. +dflash/models/ +**/*.gguf +**/*.safetensors + +# Git metadata. Submodule contents are kept; .git files inside the worktree +# are not needed at build time. +.git/ +**/.git +**/.gitignore.local + +# Local agent / IDE state. +.claude/ +.idea/ +.vscode/ + +# Misc large or volatile. +*.log +*.tmp +*.swp +**/*.bin +**/*.npy diff --git a/.gitattributes b/.gitattributes index 592097d13..e746fd25e 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,6 +1,7 @@ *.gif filter=lfs diff=lfs merge=lfs -text *.png filter=lfs diff=lfs merge=lfs -text assets/banner.png -filter -diff -merge -text +assets/docker.png -filter -diff -merge -text *.jpg filter=lfs diff=lfs merge=lfs -text *.jpeg filter=lfs diff=lfs merge=lfs -text *.mp4 filter=lfs diff=lfs merge=lfs -text diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f58c2e995..bc238c75a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -10,8 +10,8 @@ jobs: name: uv workspace (lock + sync + import smoke) runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 - - uses: astral-sh/setup-uv@v3 + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + - uses: astral-sh/setup-uv@caf0cab7a618c569241d31dcd442f54681755d39 # v3 with: version: "0.11.x" - name: Verify uv lockfile and workspace sync @@ -19,11 +19,14 @@ jobs: # full sync and builds megakernel against torch. run: bash scripts/check_uv_workspace.sh + - name: Lint Python surfaces touched by lucebox tooling + run: uv run --frozen --extra dev ruff check . + build: name: Build (cmake + uv sync --extra megakernel) runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 with: submodules: recursive token: ${{ secrets.SUBMODULE_PAT || secrets.GITHUB_TOKEN }} @@ -39,7 +42,7 @@ jobs: sub-packages: '["nvcc", "cudart-dev", "thrust", "driver-dev"]' non-cuda-sub-packages: '["libcublas-dev"]' - - uses: astral-sh/setup-uv@v3 + - uses: astral-sh/setup-uv@caf0cab7a618c569241d31dcd442f54681755d39 # v3 with: version: "0.11.x" # uv reads .python-version (3.12, matching the previous CI) and downloads the matching diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml new file mode 100644 index 000000000..bf27ffe47 --- /dev/null +++ b/.github/workflows/docker.yml @@ -0,0 +1,186 @@ +name: Docker prebuilds + +# Builds the cuda12 lucebox-hub Docker image defined in docker-bake.hcl +# and pushes it to GHCR. The bake file is the source of +# truth for arch matrices and CUDA pinning; this workflow only handles +# fetching submodules, freeing runner disk, signing in to the registry, and +# wiring the cache. + +on: + # Build + push to GHCR when a GitHub Release is published. The release tag + # becomes one of the image tags via docker/metadata-action's `type=ref, + # event=tag` + `type=semver` rules below. + release: + types: [published] + # Build + push the rolling `:cuda12` tag on every main merge so the public + # image tracks main. The metadata-action `enable=` rule below gates the + # moving tag on `github.ref == refs/heads/main`, and the build step's + # `push:` condition includes push events on main. + push: + branches: [main] + # Build-only CI guard on PRs that touch the docker surface. We never push + # from a PR — even if we wanted to, GITHUB_TOKEN on PRs from forks lacks + # `packages:write`. The point is to catch Dockerfile / bake-file / arch- + # list regressions before they land on main. + pull_request: + paths: + - Dockerfile + - docker-bake.hcl + - .dockerignore + - .github/workflows/docker.yml + - server/CMakeLists.txt + - server/src/** + - server/test/** + - server/include/** + - server/scripts/** + - server/deps/** + - server/pyproject.toml + - pyproject.toml + - uv.lock + # Manual trigger for one-off rebuilds or pre-release smoke tests. The + # `push` input controls whether the resulting images land in GHCR or only + # populate the buildx cache. + workflow_dispatch: + inputs: + push: + description: "Push images to GHCR after build" + type: boolean + default: false + +# Single in-flight build per ref. New pushes cancel the previous run so we +# don't queue 30-min compiles. +concurrency: + group: docker-${{ github.ref }} + cancel-in-progress: true + +env: + REGISTRY: ghcr.io + IMAGE_NAME: ${{ github.repository_owner }}/lucebox-hub + +jobs: + build: + name: ${{ matrix.variant }} + # ubuntu-latest = 4 CPU / 16 GB RAM / 14 GB free disk on the GitHub- + # hosted plan. The disk-free step at the top of the job claws back + # ~30 GB, which is enough to land a 14 GB image with build cache. + # CPU is the harder constraint: the fat-binary arch list can take hours + # on hosted runners. If you outgrow this: + # • Larger GitHub-hosted runners (`ubuntu-latest-8-cores`, paid) + # halve wall time. + # • A self-hosted runner with the host's nvcc avoids the + # containerised CUDA toolkit pull entirely. + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + strategy: + fail-fast: false + matrix: + variant: [cuda12] + steps: + - name: Free runner disk space + # The default ubuntu-latest image keeps ~25 GB of preinstalled + # tooling (Android SDK, .NET, Haskell, ghc, etc.) we don't need. + # Pinned action; check upstream releases before bumping. + uses: jlumbroso/free-disk-space@54081f138730dfa15788a46383842cd2f914a1be # v1.3.1 + with: + tool-cache: true + android: true + dotnet: true + haskell: true + large-packages: false # slow; preinstalled apt packages we don't need + swap-storage: true + + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + with: + # Submodule contents are needed by the cmake build (llama.cpp ggml + # subtree, mit-han-lab Block-Sparse-Attention). The Dockerfile + # asserts they're present before running cmake. + submodules: recursive + + - uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3 + + - name: Log in to GHCR + # Skip on PR runs: we never push from a PR and the token from a fork + # PR can't `packages:write` anyway. + if: github.event_name != 'pull_request' + uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Capture build identity + id: identity + # /props.build identity baked into the image. GIT_SHA is the full + # commit sha (matches `${{ github.sha }}` — short-form is fine, we + # use the full 40-char form for "exactly which weights are running" + # forensics). BUILD_TIME is ISO 8601 UTC. IMAGE_TAG is filled in + # after the metadata-action step below picks the headline tag. + run: | + echo "git_sha=${{ github.sha }}" >> "$GITHUB_OUTPUT" + echo "build_time=$(date -u +%Y-%m-%dT%H:%M:%SZ)" >> "$GITHUB_OUTPUT" + + - name: Derive image metadata + id: meta + uses: docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051 # v5 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + # Suffix every tag with the variant so future CUDA stacks can + # coexist under the same image name. Examples (using cuda12): + # ghcr.io//lucebox-hub:cuda12 (moving — main/dispatch/release) + # ghcr.io//lucebox-hub:0.3.0-cuda12 (pinned — from `lucebox-v0.3.0` tag) + # ghcr.io//lucebox-hub:feat-x-cuda12 (per branch) + # ghcr.io//lucebox-hub:sha-abc1234-cuda12 (per commit) + flavor: | + latest=false + suffix=-${{ matrix.variant }},onlatest=true + tags: | + # Moving variant tag — emitted on main, release, and any + # workflow_dispatch with push:true. The `enable=` gate keeps + # branch + PR builds from clobbering the published `:cuda12`. + type=raw,value=${{ matrix.variant }},suffix=,priority=1000,enable=${{ github.event_name == 'release' || (github.ref == 'refs/heads/main' && github.event_name != 'pull_request') || (github.event_name == 'workflow_dispatch' && inputs.push) }} + # Pinned version tag: extracts the version from a + # `lucebox-v` git tag push via hatch-vcs. Yields e.g. + # `0.3.0-cuda12` when `lucebox-v0.3.0` is pushed. + type=match,pattern=lucebox-v(\d+\.\d+\.\d+),group=1 + type=ref,event=branch + type=ref,event=tag + type=ref,event=pr + type=sha,prefix=sha- + type=semver,pattern={{version}} + type=semver,pattern={{major}}.{{minor}} + + - name: Build and push + uses: docker/bake-action@4a9a8d494466d37134e2bfca2d3a8de8fb2681ad # v5 + env: + # Wire identity into docker-bake.hcl's GIT_SHA / IMAGE_TAG / + # BUILD_TIME variables. IMAGE_TAG is `${{ steps.meta.outputs. + # version }}` — the headline tag metadata-action picked + # (e.g. `cuda12` on main, `0.3.0-cuda12` on a release tag). + # The image's /props.build will surface these so a curl can + # pin down "what binary is this exactly" without inspecting + # the registry. + GIT_SHA: ${{ steps.identity.outputs.git_sha }} + IMAGE_TAG: ${{ steps.meta.outputs.version }} + BUILD_TIME: ${{ steps.identity.outputs.build_time }} + # PR builds compile a single arch (sm_86, the RTX 3090 reference) for + # fast feedback (~20 min vs ~2 h for the full fat binary) and to stay + # under the concurrency group's pre-emption window. Release / main / + # dispatch builds keep the full consumer-GPU list so the published + # image runs on every supported card. + DFLASH_CUDA_ARCHES: ${{ github.event_name == 'pull_request' && '86' || '75;80;86;89;90;120' }} + with: + files: | + docker-bake.hcl + ${{ steps.meta.outputs.bake-file }} + targets: ${{ matrix.variant }} + push: ${{ github.event_name == 'release' || (github.event_name == 'push' && github.ref == 'refs/heads/main') || (github.event_name == 'workflow_dispatch' && inputs.push) }} + # gha cache stores layer blobs in the workflow's Actions cache, + # scoped by variant so future CUDA stacks don't evict each other. + # mode=max also caches multi-stage intermediate layers (the + # builder stage with the 30-min nvcc compile), which is the whole + # point of doing this. + set: | + ${{ matrix.variant }}.cache-from=type=gha,scope=${{ matrix.variant }} + ${{ matrix.variant }}.cache-to=type=gha,scope=${{ matrix.variant }},mode=max diff --git a/.gitignore b/.gitignore index b400bb6de..0ff34e9cf 100644 --- a/.gitignore +++ b/.gitignore @@ -79,3 +79,17 @@ fix-plan.md # Harness test artifacts .harness-work/ health + +# lucebox host-side generated config + benchmark output +.lucebox/ +models/.lucebox/ + +# Claude Code session state (worktrees, agent scratchpads) +.claude/ + +# Local tuning snapshots (not committed) +dflash/docs/tuning-snapshots/ + +# Workdir editor backup suffixes +*.git-head +*.pre-pflash-rename diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 000000000..06355d6df --- /dev/null +++ b/Dockerfile @@ -0,0 +1,282 @@ +# syntax=docker/dockerfile:1.7 + +# ─── Stage 1: builder ─────────────────────────────────────────────────────── +# CUDA_VERSION / UBUNTU_VERSION / DFLASH_CUDA_ARCHES are build args so the +# same Dockerfile can be repinned later. The prebuilt image is the +# CUDA 12.8 path: +# • lucebox-hub:cuda12 — CUDA 12.8.1, sm_75;80;86;89;90;120 +# See docker-bake.hcl for the canonical invocation. +ARG CUDA_VERSION=12.8.1 +ARG UBUNTU_VERSION=22.04 +FROM nvidia/cuda:${CUDA_VERSION}-devel-ubuntu${UBUNTU_VERSION} AS builder + +ARG DEBIAN_FRONTEND=noninteractive + +# Fat-binary CUDA arch list, semicolon-separated. Defaults cover the CUDA 12.8 +# image. dflash-supported arches in this image: +# 75 Turing RTX 2080 Ti +# 80 Ampere A100 +# 86 Ampere RTX 3090, A40, A10 +# 89 Ada RTX 4090, L40 +# 90 Hopper H100 +# 120 Blackwell RTX 5090, RTX 5090 Laptop +# Thor and GB10 prebuilt-image coverage is intentionally omitted. +# Pre-Turing arches (sm_60/61/70/72) are intentionally excluded — dflash's +# BF16/WMMA paths have no fallback below sm_75. Each arch adds ~50-200 MB +# of fat-binary kernel code and ~3-5 min of nvcc time per .cu translation +# unit. +ARG DFLASH_CUDA_ARCHES="75;80;86;89;90;120" + +RUN apt-get update && apt-get install -y --no-install-recommends \ + build-essential \ + ca-certificates \ + cmake \ + curl \ + git \ + git-lfs \ + libcurl4-openssl-dev \ + ninja-build \ + pkg-config \ + python3 \ + && rm -rf /var/lib/apt/lists/* + +# CUDA driver stub. nvidia/cuda:*-devel images ship the driver stub at +# /usr/local/cuda/lib64/stubs/libcuda.so but not as libcuda.so.1. ld follows +# the NEEDED reference inside libggml-cuda.so by SONAME (libcuda.so.1) when +# linking executables, so without this symlink + ld.so.conf entry the +# test_dflash link step fails with `undefined reference to cuMem*`. +# At runtime the host driver provides the real libcuda.so.1 via +# --gpus all; the stub is only for build-time symbol resolution. +RUN ln -sf libcuda.so /usr/local/cuda/lib64/stubs/libcuda.so.1 \ + && echo "/usr/local/cuda/lib64/stubs" > /etc/ld.so.conf.d/cuda-stubs.conf \ + && ldconfig + +WORKDIR /src + +# COPY ordering is structured to keep the CUDA build cached across +# Python-only edits. The cmake build only depends on dflash/{CMakeLists, +# include, src, test, hip_compat, deps}. Everything else (Python scripts, +# workspace pyproject manifests, lockfile, READMEs) is copied later so +# editing server.py / bench_*.py / lucebox sources doesn't invalidate the +# ~25-minute CUDA template-instantiation layer below. + +# C++ build inputs only — sources, headers, submodules, build script. +# Note: upstream rename (PR #281) moved dflash/ → server/. Source layout +# uses server/; submodule binding names still write `dflash/deps/...` +# inside .gitmodules (arbitrary identifiers; only paths matter). +COPY server/CMakeLists.txt /src/server/CMakeLists.txt +COPY server/include /src/server/include +COPY server/src /src/server/src +COPY server/test /src/server/test +COPY server/hip_compat /src/server/hip_compat +COPY server/deps /src/server/deps +# status.html: dflash_server's POST_BUILD copies server/share/status.html into +# build/share/ (server/CMakeLists.txt). Without this COPY the build links the +# server then dies on the missing source file. +COPY server/share /src/server/share + +# Submodules (`server/deps/llama.cpp`, `server/deps/Block-Sparse-Attention`) +# must be populated on the host before `docker build` — `.git/` is excluded +# by .dockerignore so we cannot re-fetch them inside the image. ggml's own +# CMakeLists also asserts this and errors with the right command if missing, +# but failing here gives a clearer message before nvcc spins up. +RUN test -f /src/server/deps/llama.cpp/ggml/CMakeLists.txt \ + || (echo "ERROR: server/deps/llama.cpp submodule not initialised. Run on host:" >&2 \ + && echo " git submodule update --init --recursive" >&2 \ + && exit 1) + +# Configure + build. `DFLASH27B_USER_CUDA_ARCHITECTURES` pins the arch list +# through dflash's own logic (skips its auto-extend rules that depend on +# nvcc version inspection); `CMAKE_CUDA_ARCHITECTURES` also gets set so the +# vendored ggml-cuda subproject picks up the same list. +# CMAKE_BUILD_WITH_INSTALL_RPATH=ON embeds CMakeLists.txt's $ORIGIN-relative +# CMAKE_INSTALL_RPATH (`$ORIGIN/deps/llama.cpp/ggml/src`, etc.) into the +# binary at link time, instead of the default absolute build-tree paths. +# Without this the binary loses its ggml shared libs after COPY to the +# runtime stage (`libggml.so.0: cannot open shared object file`). +RUN cmake -S /src/server -B /src/server/build \ + -G Ninja \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_BUILD_WITH_INSTALL_RPATH=ON \ + -DDFLASH27B_USER_CUDA_ARCHITECTURES="${DFLASH_CUDA_ARCHES}" \ + -DCMAKE_CUDA_ARCHITECTURES="${DFLASH_CUDA_ARCHES}" \ + && cmake --build /src/server/build --target test_dflash dflash_server test_server_unit --parallel + +# Prune the build tree to only what the runtime stage needs: the native server, +# test_dflash, test_server_unit, and the ggml shared libs their embedded rpath +# ($ORIGIN/deps/...) looks up. Drops ~1 GB per image of CMakeFiles/, +# libdflash27b.a (statically linked into the binaries), ninja state, +# compile_commands.json, and the template-instance .o tree from ggml-cuda. +RUN cd /src/server/build \ + && find . -mindepth 1 -maxdepth 1 \ + ! -name test_dflash ! -name dflash_server ! -name test_server_unit ! -name deps -exec rm -rf {} + \ + && find deps -mindepth 1 -type f ! -name 'lib*.so*' -delete \ + && find deps -depth -type d -empty -delete + +# Python sources, workspace manifests, lockfile, READMEs — everything the +# runtime stage needs to COPY but the cmake build does not. Editing any +# of these reuses the cached CUDA layers above and only re-runs the +# runtime stage's uv sync (~70s) instead of the full ~25-minute build. +# +# Host-side Python tooling (lucebox/, harness/) is intentionally not copied +# here: this image is the server. Such tooling can layer on top later via a +# follow-up COPY directive or a runtime bind-mount during dev. +COPY pyproject.toml uv.lock README.md /src/ +COPY server/pyproject.toml server/README.md /src/server/ +COPY server/scripts /src/server/scripts +COPY optimizations/pflash /src/optimizations/pflash +COPY optimizations/megakernel /src/optimizations/megakernel + +# ─── Stage 2: runtime ─────────────────────────────────────────────────────── +# Runtime image: ships nvidia driver libs but no nvcc / dev headers. Matches +# the builder's CUDA version so the test_dflash binary's libcudart SONAME +# resolves at runtime against the same major.minor. +FROM nvidia/cuda:${CUDA_VERSION}-runtime-ubuntu${UBUNTU_VERSION} AS runtime + +ARG DEBIAN_FRONTEND=noninteractive + +# Image identity baked in at build time and read by dflash_server at startup +# to populate /props.build (git_sha / image_tag / build_time). All three are +# wired from docker-bake.hcl, which sources them from CI metadata or local +# `git`. Missing args leave the corresponding fields empty in IMAGE_INFO, +# which dflash_server surfaces as JSON null at /props.build.* — that's the +# expected behavior on a `docker build` run without bake. +ARG GIT_SHA="" +ARG IMAGE_TAG="" +ARG BUILD_TIME="" + +RUN apt-get update && apt-get install -y --no-install-recommends \ + ca-certificates \ + curl \ + docker.io \ + libgomp1 \ + pciutils \ + && rm -rf /var/lib/apt/lists/* + +# uv manages Python 3.12 (required by the workspace) and resolves the +# lucebox-dflash + pflash members declared in pyproject.toml. +RUN curl -LsSf https://astral.sh/uv/install.sh \ + | env UV_INSTALL_DIR=/usr/local/bin UV_NO_MODIFY_PATH=1 INSTALLER_NO_MODIFY_PATH=1 sh + +# Install Python to a world-readable location, not /root/.local/share/uv/ +# (the default). The container runs as the host UID for bind-mount sanity +# (so config.toml files in $HOME are user-owned, not root-owned), and a +# non-root UID cannot traverse into root's home to exec python. Same for +# the uv cache — must be world-readable so non-root reads from it. +ENV UV_PYTHON_INSTALL_DIR=/opt/uv/python \ + UV_TOOL_DIR=/opt/uv/tools + +WORKDIR /opt/lucebox-hub + +# Workspace files for uv sync (root pyproject + lock + README + workspace +# member manifests). Each is a leaf file or small dir so layers stay tiny. +# The in-container entrypoint lives at server/scripts/entrypoint.sh and +# dispatches to either the dflash server, the lucebox Python CLI, or the +# benchmark. The host-side `lucebox.sh` is the supported way to drive this +# image; the Python CLI inside owns all orchestration logic. +COPY --from=builder /src/pyproject.toml /src/uv.lock /src/README.md /opt/lucebox-hub/ +COPY --from=builder /src/optimizations/pflash /opt/lucebox-hub/optimizations/pflash +COPY --from=builder /src/optimizations/megakernel/pyproject.toml \ + /src/optimizations/megakernel/README.md \ + /opt/lucebox-hub/optimizations/megakernel/ + +# Host-side Python tooling (lucebox/, harness/) is intentionally absent +# here: this image is the server base layer. Such tooling can layer on top +# later via a follow-up COPY directive or a runtime bind-mount during dev. + +# server: ship the entrypoint/benchmark scripts, the pyproject + README that uv +# resolves against, and the pruned build tree (binaries + .so files from the +# prune step in the builder stage). Source code, headers, tests, and submodule +# sources stay in the builder. +COPY --from=builder /src/server/scripts /opt/lucebox-hub/server/scripts +COPY --from=builder /src/server/pyproject.toml /src/server/README.md \ + /opt/lucebox-hub/server/ +COPY --from=builder /src/server/build /opt/lucebox-hub/server/build + +# Model-card sidecars resolved at startup. The server's search path +# (model_card.cpp) looks at /../share/model_cards first, so +# placing them at /opt/lucebox-hub/server/share/model_cards/ makes +# them discoverable without DFLASH_MODEL_CARDS_DIR. Copied directly +# from the build context (no builder roundtrip needed — these are +# static JSON, not compiled). +# One copy under share/; a symlink wires in the server search path so +# we don't duplicate. The C++ server binary resolves +# /../share/model_cards = server/build/../share/model_cards = +# server/share/model_cards. The canonical copy also lives at +# /opt/lucebox-hub/share/model_cards for any host-side tooling. +COPY share/model_cards /opt/lucebox-hub/share/model_cards +RUN mkdir -p /opt/lucebox-hub/server/share \ + && ln -s /opt/lucebox-hub/share/model_cards \ + /opt/lucebox-hub/server/share/model_cards + +RUN test -x /opt/lucebox-hub/server/build/test_dflash \ + && test -x /opt/lucebox-hub/server/build/dflash_server \ + && test -x /opt/lucebox-hub/server/build/test_server_unit \ + && test -f /opt/lucebox-hub/server/share/model_cards/qwen3.6-27b.json \ + && chmod +x /opt/lucebox-hub/server/scripts/entrypoint.sh + +# Image identity for /props.build. dflash_server reads this file at startup +# (path: /opt/lucebox-hub/IMAGE_INFO, three lines: git_sha, image_tag, +# build_time). Override the path with $DFLASH_IMAGE_INFO_PATH for tests. +# All three args may be empty in non-bake builds — the empty lines that +# results in are detected at read time and surface as JSON null in /props. +RUN printf '%s\n%s\n%s\n' "$GIT_SHA" "$IMAGE_TAG" "$BUILD_TIME" \ + > /opt/lucebox-hub/IMAGE_INFO + +# Register the ggml lib dir with ld.so so libggml-cpu.so (loaded transitively +# by libggml.so) resolves. CMakeLists.txt sets a `$ORIGIN/deps/...` RUNPATH +# uniformly across all linked artefacts — correct for test_dflash in +# server/build/, broken for the .so files in deps/llama.cpp/ggml/src/ which +# would need a plain `$ORIGIN`. ld.so.conf side-steps the RPATH bug without +# patching every shared lib. +RUN printf '%s\n%s\n' \ + /opt/lucebox-hub/server/build/deps/llama.cpp/ggml/src \ + /opt/lucebox-hub/server/build/deps/llama.cpp/ggml/src/ggml-cuda \ + > /etc/ld.so.conf.d/lucebox-ggml.conf \ + && ldconfig + +# Resolve Python deps for the lucebox CLI and remaining Python benchmark harness. +# Megakernel is an optional extra and is intentionally skipped — its CUDA +# extension would require nvcc + matching torch headers in this stage. +# `--no-cache` keeps wheels from being persisted in the layer; hardlink mode +# means the venv files live alongside the cache during the install but the +# cache is gone by the time the layer commits, so we don't double-pay. +ENV UV_LINK_MODE=hardlink \ + UV_NO_CACHE=1 +# --no-editable: install workspace members (pflash, lucebox-hub, and the +# lucebox-dflash server binding) as proper wheels rather than +# source-linked editable installs. Without this, hatch-vcs's build hook +# re-fires at runtime when `uv run` re-checks env consistency and tries +# to write `_version.py` into the root-owned workspace source dirs, which +# fails as a non-root user. With non-editable wheels the venv is +# self-contained and the build hook only runs once, here, with root. +RUN uv sync --no-dev --frozen --no-editable 2>/dev/null \ + || uv sync --no-dev --frozen --no-editable + +# Host wrapper CLI containers run as the invoking host uid so bind-mounted +# config/profile files are not left root-owned. Keep the uv-managed +# interpreter, the python install, and the workspace readable/executable +# for that non-root uid. UV_PYTHON_INSTALL_DIR redirects the python +# install to /opt/uv/python (set as ENV before the sync above); we still +# chmod the venv + workspace + uv-install dir so the non-root user can +# reach interpreter, scripts, and the writable directories the runtime +# might touch. +RUN chmod -R a+rX /opt/lucebox-hub/.venv /opt/lucebox-hub /opt/uv + +# Models live in server/models/ — bind-mount or volume them in. +# Example: +# docker run --rm --gpus all -p 8080:8080 \ +# -v "$PWD/server/models:/opt/lucebox-hub/server/models" \ +# lucebox-hub +# The VOLUME declaration keeps the path out of the image layer cache; the +# bind mount above replaces it with the host directory at run time. +VOLUME ["/opt/lucebox-hub/server/models"] + +ENV DFLASH_HOST=0.0.0.0 \ + DFLASH_PORT=8080 \ + DFLASH_BIN=/opt/lucebox-hub/server/build/test_dflash \ + DFLASH_SERVER_BIN=/opt/lucebox-hub/server/build/dflash_server + +EXPOSE 8080 + +ENTRYPOINT ["/opt/lucebox-hub/server/scripts/entrypoint.sh"] diff --git a/Makefile b/Makefile new file mode 100644 index 000000000..49a2700eb --- /dev/null +++ b/Makefile @@ -0,0 +1,101 @@ +# Makefile: single entry point for the common dev/CI ops on lucebox-hub. +# +# Most targets shell out to `uv` and `docker buildx bake`. Pre-release +# software: targets favor simplicity over portability (assumes bash + GNU +# coreutils + a working docker buildx + uv on PATH). +# +# Quick start: +# make help # what's available +# make lint # ruff check + format check +# make build # docker buildx bake cuda12-local --load +# make serve # docker run the local image, gemma-4-26b +# make clean # drop containers + dangling images + +.DEFAULT_GOAL := help +SHELL := /bin/bash + +# ── Build args ────────────────────────────────────────────────────────── +# Narrow the CUDA arch list to your local GPU to cut build time 5-6×: +# make build DFLASH_CUDA_ARCHES=120 +DFLASH_CUDA_ARCHES ?= 75;80;86;89;90;120 + +# Where to mount models into the container. +MODELS_DIR ?= $(HOME)/models + +# Image name (local tag the buildx bake produces). +IMAGE ?= lucebox-hub:cuda12 + +# ── Targets ───────────────────────────────────────────────────────────── + +.PHONY: help +help: ## Show this help message. + @awk 'BEGIN {FS = ":.*?## "} /^[a-zA-Z_-]+:.*?## / {printf " \033[36m%-18s\033[0m %s\n", $$1, $$2}' $(MAKEFILE_LIST) + +.PHONY: sync +sync: ## uv sync the workspace (incl. dev extras). + uv sync --extra dev + +.PHONY: lint +lint: sync ## Ruff check + format-check (no auto-fix). + uv run ruff check + uv run ruff format --check + +.PHONY: fix +fix: sync ## Ruff auto-fix + format. + uv run ruff check --fix + uv run ruff format + +.PHONY: build +build: ## Build lucebox-hub:cuda12 locally via docker buildx bake. + DFLASH_CUDA_ARCHES="$(DFLASH_CUDA_ARCHES)" docker buildx bake cuda12-local --load + +.PHONY: serve +serve: ## Run the local image, foreground. Models bind-mounted from $(MODELS_DIR). + docker run --rm --gpus all -p 8080:8080 \ + -v $(MODELS_DIR):/opt/lucebox-hub/server/models:ro \ + --name lucebox-gemma \ + $(IMAGE) serve + +.PHONY: stop +stop: ## Stop a running lucebox-gemma container. + -docker stop lucebox-gemma + -docker rm lucebox-gemma + +.PHONY: shell +shell: ## Drop into a bash shell inside the image (debug). + docker run --rm -it --gpus all $(IMAGE) shell + +.PHONY: ci-trigger-build +ci-trigger-build: ## Trigger GH Actions docker build+push for the current branch. + gh workflow run docker.yml --ref "$$(git branch --show-current)" -f push=true + @echo "view: gh run watch" + +.PHONY: clean +clean: ## Drop stopped containers, dangling images, build cache (~10 GB+). + -docker container prune -f + -docker image prune -f + -docker buildx prune -f --filter "until=24h" + +.PHONY: clean-models +clean-models: ## Remove downloaded models from $(MODELS_DIR). Destructive. + @# Guard against catastrophic overrides: MODELS_DIR=/ or empty would + @# rm -rf the host. Also reject $$HOME and other top-level user dirs to + @# avoid surprising blast radius when someone runs this with + @# MODELS_DIR=~ in muscle memory. + @set -eu; \ + dir='$(MODELS_DIR)'; \ + if [ -z "$$dir" ]; then \ + echo "ERROR: MODELS_DIR is empty; refusing to clean." >&2; exit 1; \ + fi; \ + resolved=$$(cd "$$dir" 2>/dev/null && pwd -P || echo "$$dir"); \ + case "$$resolved" in \ + /|/home|/root|/Users|"$$HOME"|/usr|/etc|/var|/opt|/bin|/sbin|/lib|/lib64|/boot|/dev|/proc|/sys|/tmp) \ + echo "ERROR: refusing to rm -rf $$resolved/*" >&2; exit 1 ;; \ + esac; \ + if [ ! -d "$$resolved" ]; then \ + echo "MODELS_DIR=$$resolved does not exist; nothing to clean."; exit 0; \ + fi; \ + echo "WARN: about to rm -rf $$resolved/*"; \ + read -r -p "Continue? [y/N] " ans; \ + [ "$$ans" = "y" ] || { echo "Aborted."; exit 0; }; \ + rm -rf -- "$$resolved"/* diff --git a/README.md b/README.md index 7fb797f8f..04886eaaa 100644 --- a/README.md +++ b/README.md @@ -151,6 +151,35 @@ python3 harness/client_test_runner.py bench \ --n-sample 3 ``` +## Quick Start With Docker + +Prebuilt images on GHCR track `main`. No CUDA toolkit, no build step: +pull, mount your weights, serve. OpenAI-compatible API on `:8000`. + + + + + + +
+ +```bash +# NVIDIA (CUDA 12+) +docker run --rm --gpus all -p 8000:8080 \ + -v "$PWD/server/models:/opt/lucebox-hub/server/models" \ + ghcr.io/luce-org/lucebox-hub:cuda12 +``` + +Drop a GGUF target into `server/models/` first, then hit +`:8000/v1/chat/completions`. Full walkthrough in the +[Docker blog](https://lucebox.com/blog/docker). + + + +Lucebox prebuilt Docker images + +
+ ## Run the Server Default: Qwen 3.6-27B Q4_K_M target + Lucebox Q4_K_M DFlash drafter on RTX 3090. DDTree budget=22, TQ3_0 KV cache, sliding FA window 2048. OpenAI-compatible HTTP on `:8000`. diff --git a/assets/docker.png b/assets/docker.png new file mode 100644 index 000000000..bc9f3b7f6 Binary files /dev/null and b/assets/docker.png differ diff --git a/docker-bake.hcl b/docker-bake.hcl new file mode 100644 index 000000000..22149fd4a --- /dev/null +++ b/docker-bake.hcl @@ -0,0 +1,110 @@ +# docker-bake.hcl — Lucebox hub prebuild matrix. +# +# Single CUDA 12 image from one Dockerfile. Additional CUDA stacks are +# intentionally omitted. +# +# scripts/build_image.sh # version-derived local build (preferred) +# docker buildx bake cuda12-local # raw local build; tagged lucebox-hub:cuda12 +# docker buildx bake cuda12 # CI target; tags come from metadata-action +# # Arches: sm_75;80;86;89;90;120 +# +# Pre-Turing arches (Pascal sm_60/61, Volta sm_70) are intentionally +# excluded — dflash's kernels assume sm_75+ with no fallback below +# (dflash/CMakeLists.txt:276). +# +# The CI `cuda12` target takes tags from docker/metadata-action. The local +# `cuda12-local` target tags `lucebox-hub:cuda12` (moving) and, when +# `VERSION` is set, also tags `lucebox-hub:-cuda12` (pinned). +# `scripts/build_image.sh` is the recommended driver: it computes the +# version from `git describe --tags --match 'lucebox-v*'` so the image +# carries the same git-derived version as the Python packages (hatch-vcs). +# +# Override the registry / version via env: `VERSION=0.3.0 \ +# REGISTRY=ghcr.io/luce-org/ docker buildx bake cuda12-local`. + +variable "REGISTRY" { default = "" } +# `VERSION` should be the bare version (e.g. `0.2.7.dev0+gabc1234`) so the +# image tag composes cleanly. Empty means "no pinned tag, just the moving +# variant tag" — keeps `docker buildx bake cuda12-local` working with zero +# config. +variable "VERSION" { default = "" } +# `TAG` is the legacy override (pre-VERSION). Still honored for back-compat +# but new callers should use `VERSION`. +variable "TAG" { default = "" } + +# Fat-binary CUDA arch list. Defaults to all supported arches so the +# released image runs on every consumer/datacenter GPU we target. Local +# dev builds can narrow this to the host's compute capability to skip the +# 5-6× CUDA template recompile cost: +# +# DFLASH_CUDA_ARCHES=120 docker buildx bake cuda12-local --load +# +# (RTX 5090 / 5090 Laptop = 120, RTX 4090 = 89, RTX 3090 = 86, H100 = 90, +# A100 = 80, RTX 2080 Ti = 75.) Use a semicolon-separated list to include +# multiple arches. +variable "DFLASH_CUDA_ARCHES" { default = "75;80;86;89;90;120" } + +# Image identity stamped into /opt/lucebox-hub/IMAGE_INFO at build time and +# surfaced under /props.build at runtime (git_sha, image_tag, build_time). +# CI sets all three from the workflow context; local builds get a best- +# effort `git rev-parse` for GIT_SHA + empty IMAGE_TAG/BUILD_TIME (those +# come from CI metadata-action and the workflow timestamp, neither of +# which is available offline). Empty values turn into JSON null at /props. +variable "GIT_SHA" { default = "" } +variable "IMAGE_TAG" { default = "" } +variable "BUILD_TIME" { default = "" } + +# Image tag list. Default (no VERSION / no TAG) emits just the moving +# `lucebox-hub:cuda12`. With VERSION set we also emit a pinned +# `lucebox-hub:-cuda12`. Both point at the same image so users +# can pull either form. TAG (legacy) still works as a single-tag override. +# +# Docker tag charset is [A-Za-z0-9_.-], so PEP 440 local-version segments +# (e.g. `0.2.7.dev0+gabc1234` from hatch-vcs on a post-tag dev commit) +# need their `+` replaced before they can be used as a tag. We map `+` → +# `-` so the pinned tag becomes e.g. `0.2.7.dev0-gabc1234-cuda12`. +sanitized_version = regex_replace(VERSION, "\\+", "-") +function "image_tags" { + params = [variant] + result = TAG != "" ? ["${REGISTRY}lucebox-hub:${TAG}-${variant}"] : (VERSION != "" ? ["${REGISTRY}lucebox-hub:${variant}", "${REGISTRY}lucebox-hub:${sanitized_version}-${variant}"] : ["${REGISTRY}lucebox-hub:${variant}"]) +} + +group "default" { + targets = ["cuda12-local"] +} + +# CI integration. docker/metadata-action in .github/workflows/docker.yml +# emits a bake-file that defines a `docker-metadata-action` target carrying +# tags + labels derived from the ref. Both build targets inherit from it. +# Local `docker buildx bake` invocations do not provide the metadata-action +# file, so this empty target keeps inheritance valid. +target "docker-metadata-action" {} + +# ── CUDA 12.8 ─────────────────────────────────────────────────────────────── +# CUDA 12.8 matches the uv-managed PyTorch cu128 stack and carries current-gen +# consumer Blackwell sm_120 coverage. Thor/GB10 variants stay out of this +# build matrix. +target "_cuda12-base" { + context = "." + dockerfile = "Dockerfile" + args = { + CUDA_VERSION = "12.8.1" + UBUNTU_VERSION = "22.04" + DFLASH_CUDA_ARCHES = DFLASH_CUDA_ARCHES + # /props.build identity. CI passes these as env vars from the + # workflow context; local builds rely on the variables' defaults + # (empty strings → JSON null at /props.build.*). + GIT_SHA = GIT_SHA + IMAGE_TAG = IMAGE_TAG + BUILD_TIME = BUILD_TIME + } +} + +target "cuda12" { + inherits = ["_cuda12-base", "docker-metadata-action"] +} + +target "cuda12-local" { + inherits = ["_cuda12-base"] + tags = image_tags("cuda12") +} diff --git a/pyproject.toml b/pyproject.toml index 30dc2ee58..b68870fef 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -14,7 +14,26 @@ dependencies = [ [project.optional-dependencies] megakernel = ["qwen35-megakernel-bf16"] -dev = ["pytest>=8"] +dev = ["pytest>=8", "mypy>=1.10,<2", "ruff>=0.14,<1"] + +[tool.ruff] +target-version = "py312" +line-length = 100 +# Staged lint adoption for the Python surfaces touched by the Docker, +# benchmark, autotune, and host-CLI stack. Untouched server-adjacent scripts and +# vendored dependencies stay outside this gate until they are cleaned up. +# +# No Python package at the repo root to lint yet; workspace members add +# their own include lists. The pyproject-driven ruff config still applies. +include = [] +extend-exclude = [ + "dflash/deps", + "megakernel", + "pflash", +] + +[tool.ruff.lint] +select = ["E", "F", "I", "UP", "B"] [tool.uv] package = false @@ -31,6 +50,8 @@ package = false no-build-isolation-package = ["qwen35-megakernel-bf16"] [tool.uv.workspace] +# Workspace members. Keeping the list to the packages that live in this +# repo lets `uv lock --check` / `uv sync --frozen` pass. members = ["server", "optimizations/megakernel", "optimizations/pflash"] [tool.uv.sources] @@ -43,3 +64,8 @@ torch = { index = "pytorch-cu128" } name = "pytorch-cu128" url = "https://download.pytorch.org/whl/cu128" explicit = true + +[dependency-groups] +dev = [ + "pytest>=9.0.3", +] diff --git a/scripts/build_image.sh b/scripts/build_image.sh new file mode 100755 index 000000000..38bd34e03 --- /dev/null +++ b/scripts/build_image.sh @@ -0,0 +1,72 @@ +#!/usr/bin/env bash +# scripts/build_image.sh — Build the cuda12 lucebox-hub image with a +# git-derived version tag matching the Python packages' hatch-vcs scheme. +# +# Tagging: +# * Untagged tree → lucebox-hub:cuda12 (moving) +# * Tagged `lucebox-v0.3.0` (clean checkout): +# → lucebox-hub:cuda12 +# → lucebox-hub:0.3.0-cuda12 +# * Post-tag dev commit / dirty: +# → lucebox-hub:cuda12 +# → lucebox-hub:0.3.0-3-gabc1234-cuda12 (-dirty suffix +# appended when working tree is dirty) +# +# Single source of truth is the `lucebox-v*` git tag — same regex hatch-vcs +# uses for the Python wheel version, so the image version always matches. +# +# Usage: +# scripts/build_image.sh # build, no load (cache-fill) +# scripts/build_image.sh --load # load into local docker +# scripts/build_image.sh --push # push to $REGISTRY (must end in /) +# +# Env overrides: +# REGISTRY=ghcr.io/luce-org/ override image registry prefix +# DFLASH_CUDA_ARCHES=120 narrow arch list (skip the multi-arch compile) + +set -euo pipefail + +# Resolve to the repo root: prefer git's view (works from any subdir), else +# fall back to one level up from this script (scripts/build_image.sh → +# repo root). +cd "$(git rev-parse --show-toplevel 2>/dev/null || (cd "$(dirname "$0")/.." && pwd))" + +# Derive the version. `git describe --match 'lucebox-v*'` finds the nearest +# matching annotated tag and appends commits-past + sha if not on the tag +# itself. `--always` keeps it from failing on a brand-new clone with no +# tags. `--dirty` appends `-dirty` when the working tree differs from HEAD. +raw=$(git describe --tags --match 'lucebox-v*' --always --dirty 2>/dev/null || true) +# Strip the `lucebox-v` prefix when present (clean releases). On a fresh +# tree with no matching tags, `git describe --always` returns the short +# sha — keep it as-is so the image still gets a sensible pinned tag. +VERSION="${raw#lucebox-v}" + +if [ -z "$VERSION" ]; then + echo "[build_image] no git description available — tagging :cuda12 only" +else + echo "[build_image] version=$VERSION (from git describe)" +fi + +export VERSION +# `REGISTRY` composes into the bake tag as `${REGISTRY}lucebox-hub:…`, so +# the value MUST end with `/` (e.g. `ghcr.io/luce-org/`). Normalize a +# trailing slash so callers that forget it (`REGISTRY=ghcr.io/luce-org`) +# get the right image name rather than `ghcr.io/luce-orglucebox-hub:…`. +REGISTRY="${REGISTRY:-}" +if [ -n "$REGISTRY" ] && [ "${REGISTRY: -1}" != "/" ]; then + REGISTRY="${REGISTRY}/" +fi +export REGISTRY +export DFLASH_CUDA_ARCHES="${DFLASH_CUDA_ARCHES:-75;80;86;89;90;120}" + +# /props.build identity (baked into /opt/lucebox-hub/IMAGE_INFO and surfaced +# at /props.build). All best-effort for local builds: +# * GIT_SHA — `git rev-parse HEAD` (full sha; empty on a non-git tree) +# * BUILD_TIME — UTC ISO 8601 timestamp +# * IMAGE_TAG — falls back to "cuda12" (the moving tag) unless caller +# pre-exported one. CI replaces this with the metadata-action version. +export GIT_SHA="${GIT_SHA:-$(git rev-parse HEAD 2>/dev/null || true)}" +export BUILD_TIME="${BUILD_TIME:-$(date -u +%Y-%m-%dT%H:%M:%SZ)}" +export IMAGE_TAG="${IMAGE_TAG:-${VERSION:+${VERSION}-}cuda12}" + +exec docker buildx bake cuda12-local "$@" diff --git a/server/scripts/entrypoint.sh b/server/scripts/entrypoint.sh new file mode 100755 index 000000000..f35e295c4 --- /dev/null +++ b/server/scripts/entrypoint.sh @@ -0,0 +1,532 @@ +#!/usr/bin/env bash +# In-container ENTRYPOINT for lucebox-hub. +# +# Normal path: the host-side `lucebox` CLI has already populated every +# DFLASH_* env var from its detection / autotune sweep, so this script +# just resolves paths and execs the native dflash_server binary. +# +# Fallback path: a user runs the image directly (`docker run --gpus all +# ghcr.io/luce-org/lucebox-hub:cuda12`) with no env-var prep. We then do a +# minimal VRAM-tiered autotune — same tiers as `lucebox autotune`, kept in +# sync by hand. Anything more elaborate (driver-version probes, AMD paths, +# lspci fallbacks) belongs in the host CLI, not here. + +set -euo pipefail + +# Honor a pre-set DFLASH_DIR (used by the host-side smoke tests to drive +# the entrypoint with a synthetic models/draft layout). In the shipped +# image this var is unset, so the fallback is the normal install prefix. +DFLASH_DIR="${DFLASH_DIR:-/opt/lucebox-hub/server}" + +info() { printf '\033[1;34m[INFO]\033[0m %s\n' "$*"; } +warn() { printf '\033[1;33m[WARN]\033[0m %s\n' "$*"; } +die() { printf '\033[1;31m[ERROR]\033[0m %s\n' "$*" >&2; exit 1; } + +# ── arg dispatch ─────────────────────────────────────────────────────────── +# `serve` (default) — start the OpenAI-compatible server. +# `shell` — drop into bash inside the container (debug). +# `lucebox` — dispatch to the Python CLI. Any subcommand +# `lucebox.sh` doesn't handle on the host arrives here +# (check, config, pull, print-run, smoke, …). +# `python` or anything else +# — pass through to exec, so `docker run … python -m foo` +# still works for dev. +SUBCMD="${1:-serve}" +[ $# -gt 0 ] && shift || true + +LUCEBOX_PKG="/opt/lucebox-hub" + +case "$SUBCMD" in + lucebox) + # --no-sync: the venv was fully populated at image build time + # (`uv sync --no-editable` in the Dockerfile). Skipping the env + # consistency check here prevents hatch-vcs from firing its + # `_version.py` write hook against the read-only workspace source + # dirs and crashing the entire subcommand. + exec uv run --no-sync --directory "$LUCEBOX_PKG" python -m lucebox "$@" + ;; + shell) + exec /bin/bash "$@" + ;; + serve|"") + : # fall through to server startup below + ;; + *) + exec "$SUBCMD" "$@" + ;; +esac + +# ── HOST_INFO (host-identity capture) ────────────────────────────────────── +# Write /opt/lucebox-hub/HOST_INFO as JSON before exec'ing dflash_server. +# The C++ server reads this file at startup and surfaces the parsed JSON +# under /props.host. Mirrors the IMAGE_INFO pattern (server_main.cpp +# read_image_info) but in JSON instead of KEY=VALUE — host facts have +# nested structure (gpu array, multi-field per GPU) that doesn't fit a +# flat KEY=VALUE layout. +# +# Inputs: the LUCEBOX_HOST_* env vars set by the host wrapper's +# probe_host(). When none are set (e.g. someone ran `docker run` directly, +# bypassing lucebox.sh), we still write a stub `{"source":"unknown", ...}` +# so the C++ side doesn't have to special-case missing-vs-blank. +# +# Failure is never fatal — the host_info file is informational. A +# write-failure (read-only FS, etc.) gets a warning and we continue. +write_host_info() { + local target="/opt/lucebox-hub/HOST_INFO" + local tmp="${target}.tmp.$$" + local collected_at + collected_at=$(date -u +%FT%TZ 2>/dev/null || echo "") + # If any LUCEBOX_HOST_* var was supplied, the source is "lucebox.sh" + # (the host wrapper probed and forwarded these via -e). Otherwise the + # container was launched outside the wrapper — we still emit a stub + # so the C++ side can read /props.host without special-casing missing. + local source_tag="unknown" + local collector_tag="entrypoint.sh" + if [ -n "${LUCEBOX_HOST_OS_PRETTY:-}" ] \ + || [ -n "${LUCEBOX_HOST_KERNEL:-}" ] \ + || [ -n "${LUCEBOX_HOST_GPU_LIST_CSV:-}" ] \ + || [ -n "${LUCEBOX_HOST_CPU_MODEL:-}" ]; then + source_tag="lucebox.sh" + collector_tag="lucebox.sh" + fi + + if ! _build_host_info_json "$source_tag" "$collector_tag" "$collected_at" > "$tmp" 2>/dev/null; then + warn "Failed to build HOST_INFO JSON — skipping" + rm -f "$tmp" 2>/dev/null || true + return 0 + fi + if ! mv -f "$tmp" "$target" 2>/dev/null; then + warn "Failed to write $target (continuing without it)" + rm -f "$tmp" 2>/dev/null || true + return 0 + fi + info "Wrote $target (source=$source_tag)" +} + +# Build the HOST_INFO JSON on stdout. Real JSON escape via python3 (always +# present in the runtime image — uv pulls it in for the venv stage) with +# a bash fallback for parsing emergencies (broken venv, debug invocations +# from a minimal base). The bash fallback covers the realistic char set +# that leaks from lscpu / /etc/os-release / nvidia-smi (backslash, quote, +# newline, tab, CR); the python path covers every JSON-illegal char +# including the full U+0000-U+001F control range, so a misbehaved upstream +# can't silently invalidate the entire HOST_INFO and turn /props.host +# into null on the C++ side (which silently drops a parse-failed block). +_json_escape() { + # Read from $1, emit on stdout. No quotes around the result — the + # caller wraps with `"..."`. + if command -v python3 >/dev/null 2>&1; then + # json.dumps emits `"…escaped…"`; strip the surrounding quotes + # so callers can keep their existing `"..."` wrap convention. + python3 -c ' +import json, sys +out = json.dumps(sys.argv[1]) +sys.stdout.write(out[1:-1]) +' "$1" + return + fi + local s="$1" + s="${s//\\/\\\\}" + s="${s//\"/\\\"}" + s="${s//$'\n'/\\n}" + s="${s//$'\r'/\\r}" + s="${s//$'\t'/\\t}" + printf '%s' "$s" +} + +# Emit a JSON value for a string field. Empty input → JSON null. Caller +# embeds the result directly (no leading/trailing whitespace). +_json_str_or_null() { + if [ -z "${1:-}" ]; then + printf 'null' + else + printf '"%s"' "$(_json_escape "$1")" + fi +} + +# Emit a JSON value for an integer field. Empty / non-numeric → null. +_json_int_or_null() { + local v="${1:-}" + if [ -z "$v" ] || ! [[ "$v" =~ ^-?[0-9]+$ ]]; then + printf 'null' + else + printf '%s' "$v" + fi +} + +# Parse the LUCEBOX_HOST_GPU_LIST_CSV (whatever +# `nvidia-smi --query-gpu=index,uuid,pci.bus_id,name,compute_cap,memory.total,power.limit +# --format=csv,noheader` produced on the host) into a JSON +# array. Empty CSV → "[]". Each row becomes one object. +_emit_gpu_array() { + local csv="${LUCEBOX_HOST_GPU_LIST_CSV:-}" + if [ -z "$csv" ]; then + printf '[]' + return + fi + local out="[" first=1 + while IFS= read -r line; do + [ -z "$line" ] && continue + # Trim surrounding whitespace from each field. nvidia-smi prints + # `0, GPU-abc..., 00000000:01:00.0, NVIDIA RTX 5090, 12.0, 24576 MiB, 175.00 W`. + # Some driver builds emit bare `,` delimiters with no trailing space — + # split on `,` alone and trim whitespace per field so both forms parse. + local idx uuid pci name cc mem plimit + IFS=',' read -r idx uuid pci name cc mem plimit <<<"$line" + idx=$(printf '%s' "$idx" | sed 's/^[[:space:]]*//; s/[[:space:]]*$//') + uuid=$(printf '%s' "$uuid" | sed 's/^[[:space:]]*//; s/[[:space:]]*$//') + pci=$(printf '%s' "$pci" | sed 's/^[[:space:]]*//; s/[[:space:]]*$//') + name=$(printf '%s' "$name" | sed 's/^[[:space:]]*//; s/[[:space:]]*$//') + cc=$(printf '%s' "$cc" | sed 's/^[[:space:]]*//; s/[[:space:]]*$//') + mem=$(printf '%s' "$mem" | sed 's/^[[:space:]]*//; s/[[:space:]]*$//') + plimit=$(printf '%s' "$plimit" | sed 's/^[[:space:]]*//; s/[[:space:]]*$//') + # Strip units. "24576 MiB" → 24576; "175.00 W" → 175 (truncate). + local mem_mib vram_gb power_w + mem_mib=$(printf '%s' "$mem" | awk '{print $1+0}') + vram_gb="" + if [ -n "$mem_mib" ] && [ "$mem_mib" -gt 0 ] 2>/dev/null; then + vram_gb=$((mem_mib / 1024)) + fi + power_w=$(printf '%s' "$plimit" | awk '{printf "%d", $1+0.5}') + if [ "$first" = "1" ]; then + first=0 + else + out+="," + fi + out+="{\"index\":$(_json_int_or_null "$idx"),\"uuid\":$(_json_str_or_null "$uuid")," + out+="\"pci_bus_id\":$(_json_str_or_null "$pci"),\"name\":$(_json_str_or_null "$name")," + out+="\"sm\":$(_json_str_or_null "$cc"),\"vram_gb\":$(_json_int_or_null "$vram_gb")," + out+="\"power_limit_w\":$(_json_int_or_null "$power_w")}" + done <<<"$csv" + out+="]" + printf '%s' "$out" +} + +_build_host_info_json() { + local source_tag="$1" collector_tag="$2" collected_at="$3" + printf '{' + printf '"os_pretty":%s,' "$(_json_str_or_null "${LUCEBOX_HOST_OS_PRETTY:-}")" + printf '"kernel":%s,' "$(_json_str_or_null "${LUCEBOX_HOST_KERNEL:-}")" + printf '"wsl_version":%s,' "$(_json_str_or_null "${LUCEBOX_HOST_WSL_VERSION:-}")" + printf '"docker_version":%s,' "$(_json_str_or_null "${LUCEBOX_HOST_DOCKER_VERSION:-}")" + printf '"nvidia_driver":%s,' "$(_json_str_or_null "${LUCEBOX_HOST_DRIVER_VERSION:-}")" + printf '"nvidia_ctk_version":%s,' "$(_json_str_or_null "${LUCEBOX_HOST_NVIDIA_CTK_VERSION:-}")" + printf '"cpu_model":%s,' "$(_json_str_or_null "${LUCEBOX_HOST_CPU_MODEL:-}")" + printf '"nproc":%s,' "$(_json_int_or_null "${LUCEBOX_HOST_NPROC:-}")" + printf '"ram_gb":%s,' "$(_json_int_or_null "${LUCEBOX_HOST_RAM_GB:-}")" + printf '"gpus":%s,' "$(_emit_gpu_array)" + printf '"cuda_visible_devices":%s,' "$(_json_str_or_null "${LUCEBOX_HOST_CUDA_VISIBLE_DEVICES:-}")" + printf '"source":%s,' "$(_json_str_or_null "$source_tag")" + printf '"collector":%s,' "$(_json_str_or_null "$collector_tag")" + printf '"collected_at":%s' "$(_json_str_or_null "$collected_at")" + printf '}\n' +} + +write_host_info + +# ── detect ───────────────────────────────────────────────────────────────── +# nvidia-smi is always present here (--gpus all wires the driver in). +GPU_VRAM_GB=0 +if command -v nvidia-smi &>/dev/null; then + if mem_mib=$(nvidia-smi --query-gpu=memory.total --format=csv,noheader,nounits 2>/dev/null \ + | head -1) && [ -n "$mem_mib" ]; then + GPU_VRAM_GB=$((mem_mib / 1024)) + fi +fi +GPU_COUNT=0 +if command -v nvidia-smi &>/dev/null; then + GPU_COUNT=$(nvidia-smi -L 2>/dev/null | awk '/^GPU /{n++} END{print n+0}') || GPU_COUNT=0 +fi + +# ── fallback autotune (only fills unset env) ─────────────────────────────── +# Keep these tiers in lockstep with lucebox::autotune_env on the host. The +# divergence we accept is the lower-VRAM error tier — the host CLI refuses +# to start there with a clear message; here we just warn and let the server +# decide whether it can load. + +if [ "$GPU_VRAM_GB" -gt 0 ]; then + IS_WSL=0 + if grep -qi microsoft /proc/version 2>/dev/null || [ -e /proc/sys/fs/binfmt_misc/WSLInterop ]; then + IS_WSL=1 + fi + if [ "$GPU_VRAM_GB" -lt 12 ]; then + : "${DFLASH_LAZY:=1}" + : "${DFLASH_MAX_CTX:=4096}" + warn "VRAM ${GPU_VRAM_GB} GB < 12 GB — 27B target unlikely to fit" + elif [ "$GPU_VRAM_GB" -lt 22 ]; then + : "${DFLASH_LAZY:=1}" + : "${DFLASH_MAX_CTX:=32768}" + elif [ "$GPU_VRAM_GB" -lt 32 ]; then + : "${DFLASH_LAZY:=1}" + if [ "$IS_WSL" = "1" ]; then + : "${DFLASH_BUDGET:=16}" + : "${DFLASH_MAX_CTX:=65536}" + else + : "${DFLASH_MAX_CTX:=98304}" + fi + elif [ "$GPU_VRAM_GB" -lt 48 ]; then + : "${DFLASH_MAX_CTX:=131072}" + else + : "${DFLASH_PREFIX_CACHE_SLOTS:=0}" + : "${DFLASH_MAX_CTX:=131072}" + fi +fi + +: "${DFLASH_BIN:=$DFLASH_DIR/build/test_dflash}" +: "${DFLASH_SERVER_BIN:=$DFLASH_DIR/build/dflash_server}" +: "${DFLASH_HOST:=0.0.0.0}" +: "${DFLASH_PORT:=8080}" +: "${DFLASH_BUDGET:=22}" +: "${DFLASH_MAX_CTX:=16384}" +: "${DFLASH_LAZY:=0}" +: "${DFLASH_PREFIX_CACHE_SLOTS:=0}" +: "${DFLASH_PREFILL_CACHE_SLOTS:=0}" +: "${DFLASH_CACHE_TYPE_K:=}" +: "${DFLASH_CACHE_TYPE_V:=}" +: "${DFLASH_VERBOSE:=0}" +: "${DFLASH_TARGET:=}" +: "${DFLASH_DRAFT:=$DFLASH_DIR/models/draft}" +: "${DFLASH_PREFILL_MODE:=off}" +: "${DFLASH_PREFILL_KEEP:=0.05}" +: "${DFLASH_PREFILL_THRESHOLD:=32000}" +: "${DFLASH_PREFILL_DRAFTER:=}" +# Phase-1 (thinking) cap when a request opts into thinking. Default mirrors +# antirez/ds4 ds4_eval.c: think_max_tokens = max_tokens(16000) - hard_limit +# reply budget(512) = 15488. The server's own hardcoded default is 10000; +# overriding here aligns ds4-eval and similar reasoning benches with upstream. +: "${DFLASH_THINK_MAX:=15488}" +# Soft-close thinking termination dial (PR #326). Lets the AR loop force +# early when the close-token logit comes within this probability +# ratio of the chosen-token logit. Range [0.0, 1.0]; 0.0 = disabled (server +# default, byte-identical to pre-change behavior). 0.5 = close when close +# is within 2× of chosen; 0.9 = aggressive (close when close is within +# ~10% of chosen). Only emitted to the server CLI when nonzero so unset +# reproduces the server's own default. Qwen3.5/3.6 AR path only in v1. +: "${DFLASH_THINK_SOFT_CLOSE_MIN_RATIO:=0.0}" +# Diagnostic: when "1", forward --debug-thinking-logits to the server so +# the AR loop emits per-step [soft-trace] lines for fitting a sliding- +# ratio curve. Heavy stderr; operator-only. Default off. +: "${DFLASH_DEBUG_THINKING_LOGITS:=0}" +# Flash-attention sliding-window on full-attention layers. 0 = server's +# stock full attention. Sparse decode windows (e.g. 2048-8192) bound +# the compute on long prompts for gemma4's hybrid iSWA without changing +# the KV footprint. Only emitted to the server CLI when nonzero so +# unset reproduces the server's own default unchanged. +: "${DFLASH_FA_WINDOW:=0}" + +# ── auto-detect target ───────────────────────────────────────────────────── +# Target .gguf is typically 10-30 GB (Q4_K_M). Drafts are 1-2 GB (Q8_0 / Q4) +# and live under models/draft/ or have a dflash- prefix. The 5 GB threshold +# excludes drafts cleanly without needing to parse GGUF arch metadata. +# +# CRITICAL UX RULE: when multiple candidate targets exist, we DO NOT silently +# pick one based on filename pattern. That hid a real bug for the matrix +# bench — a hardcoded Qwen3.6 preference made the container run the wrong +# model when both gemma4 and qwen3.6 GGUFs were present, and the operator +# only noticed when the bench numbers came out wrong. Either set +# DFLASH_TARGET=... explicitly, or have exactly one .gguf in models/. +if [ -z "$DFLASH_TARGET" ] && [ -d "$DFLASH_DIR/models" ]; then + # Collect candidates: .gguf files ≥5 GB (target-sized), excluding + # anything under models/draft/. Sort alphabetically for determinism. + mapfile -t TARGET_CANDIDATES < <( + find -L "$DFLASH_DIR/models" -maxdepth 4 -type f -name '*.gguf' \ + -size +5G \ + -not -path '*/draft/*' \ + -printf '%p\n' 2>/dev/null \ + | sort + ) + case "${#TARGET_CANDIDATES[@]}" in + 0) + : # fall through to the missing-target die below + ;; + 1) + DFLASH_TARGET="${TARGET_CANDIDATES[0]}" + info "Auto-detected target: $(basename "$DFLASH_TARGET")" + ;; + *) + # Refuse to guess: silently picking the wrong target has burned + # us before (bench numbers come out wrong, only noticed after the + # fact). Force the operator to disambiguate via DFLASH_TARGET. + warn "Multiple candidate target GGUFs in $DFLASH_DIR/models. Refusing to auto-select." + warn "Set DFLASH_TARGET= to choose one. Candidates:" + for c in "${TARGET_CANDIDATES[@]}"; do + warn " $c" + done + die "Ambiguous target: set DFLASH_TARGET= from the candidates above." + ;; + esac +fi + +if [ -z "$DFLASH_TARGET" ] || [ ! -f "$DFLASH_TARGET" ]; then + die "No target GGUF found. Mount a model dir: -v /host/models:/opt/lucebox-hub/server/models, or set DFLASH_TARGET=." +fi +[ -x "$DFLASH_SERVER_BIN" ] || die "dflash_server binary missing at $DFLASH_SERVER_BIN (image build failed?)" + +# Qwen3.6 DFlash drafters use sliding-window attention in the draft. Some GGUFs +# carry this metadata directly; keep the documented env override as the startup +# default so older drafts behave like the autotune-sweep path. +case "$(basename "$DFLASH_TARGET")" in + *Qwen3.6*|*qwen3.6*) + if [ -z "${DFLASH27B_DRAFT_SWA:-}" ]; then + export DFLASH27B_DRAFT_SWA=2048 + info "Autotune: DFLASH27B_DRAFT_SWA=2048 (Qwen3.6 draft SWA)" + fi + ;; +esac + +# Common host layouts use ~/models/qwen3.6-27b-dflash as an absolute symlink +# rather than a literal models/draft directory. If the default is absent, find +# that draft before deciding to run without DFlash. +if [ "$DFLASH_DRAFT" = "$DFLASH_DIR/models/draft" ] && [ ! -e "$DFLASH_DRAFT" ]; then + for cand in "$DFLASH_DIR/models/qwen3.6-27b-dflash" \ + "$DFLASH_DIR/models/Qwen3.6-27B-DFlash" \ + "$DFLASH_DIR/models/dflash"; do + if [ -e "$cand" ]; then + DFLASH_DRAFT="$cand" + break + fi + done +fi + +# Draft: directory holding GGUF/safetensors, or a direct draft file. +# The native dflash_server expects --draft to be a FILE path (not a dir). +# If DFLASH_DRAFT points at a directory, resolve it to a draft GGUF inside. +# +# Draft files are arch-specific: a draft trained for qwen3.6 has a fc +# weight shape that only divides evenly into the qwen3.6 target's hidden +# size, and crashes hard at spec-decode time when fed gemma4 (or vice +# versa) — see Gemma4Backend draft-incompatibility check. So when the +# draft dir contains multiple drafts (e.g. a host with both qwen3.6 and +# gemma4 drafts pre-downloaded), pick the one whose filename matches the +# target's family. Falls back to the generic dflash-draft-*.gguf pattern +# (legacy qwen3.6-only behavior) when the target family is unknown. +DRAFT_ARG="$DFLASH_DRAFT" +if [ -d "$DFLASH_DRAFT" ]; then + # Derive a target-family hint from the target filename. Matching the + # GGUF arch metadata would be cleaner but requires parsing the header + # in shell; the filename convention is enforced upstream by the + # publish-side dflash quantize scripts. + TARGET_BASENAME="$(basename "$DFLASH_TARGET" .gguf 2>/dev/null)" + # Use -iname (case-insensitive) throughout so both naming conventions + # work: legacy "dflash-gemma-4-31b-*.gguf" and the Lucebox HF repo's + # "gemma-4-31B-it-DFlash-q8_0.gguf". Glob list is family-specific first, + # then generic dflash-draft-*.gguf legacy, then last-resort *.gguf. + # The 31B match in the Lucebox repo uses capital B in the filename — + # -iname handles that without needing to enumerate every case form. + case "$(echo "$TARGET_BASENAME" | tr 'A-Z' 'a-z')" in + *gemma-4-26b*|*gemma4-26b*) + FAMILY_GLOBS=('*gemma*4*26b*dflash*.gguf' '*dflash*gemma*4*26b*.gguf') ;; + *gemma-4-31b*|*gemma4-31b*) + FAMILY_GLOBS=('*gemma*4*31b*dflash*.gguf' '*dflash*gemma*4*31b*.gguf') ;; + *gemma-4*|*gemma4*) + FAMILY_GLOBS=('*gemma*4*dflash*.gguf' '*dflash*gemma*4*.gguf') ;; + *qwen3.6*|*qwen36*) + FAMILY_GLOBS=('dflash-draft-3.6-*.gguf' '*qwen*3.6*dflash*.gguf') ;; + *) + FAMILY_GLOBS=() ;; + esac + + DRAFT_FILE="" + # Track which glob actually matched so the info() log can show whether + # we picked via the family-specific pattern or fell back to a generic + # one. Initialize empty up front — `set -u` will fire if we read the + # var without an assignment having run, and the for-loop below may + # exit on the very first iteration without entering the body. + DRAFT_FAMILY_GLOB="" + # Family-specific globs first (most specific). Then the legacy + # `dflash-draft-*.gguf` for single-draft setups. Then the generic + # `*.gguf` / safetensors fallbacks. + GENERIC_GLOBS=('dflash-draft-*.gguf' '*dflash*.gguf' '*.gguf' 'model.safetensors' '*.safetensors') + family_count="${#FAMILY_GLOBS[@]}" + i=0 + for pattern in "${FAMILY_GLOBS[@]}" "${GENERIC_GLOBS[@]}"; do + # Sort matches lexicographically so the pick is deterministic across + # filesystems (find's traversal order is filesystem-dependent without + # an explicit sort). First lexicographic match wins. + DRAFT_FILE="$(find -L "$DFLASH_DRAFT" -maxdepth 4 -type f -iname "$pattern" -print 2>/dev/null | sort | head -n 1)" + if [ -n "$DRAFT_FILE" ]; then + # Mark the family-specific match so the log line below can + # distinguish "matched on family hint" from "generic fallback". + if [ "$i" -lt "$family_count" ]; then + DRAFT_FAMILY_GLOB="$pattern" + fi + break + fi + i=$((i + 1)) + done + # Defensive: every read of DRAFT_FAMILY_GLOB below must survive `set -u` + # even if the init on line ~257 was somehow skipped (e.g. a future refactor + # that moves the init out of this block, or a partial-rewrite during a + # rebase that drops it). Coalesce-to-empty inline so a regression can't + # re-trip the unbound-variable crash that fired on the sindri sweep with + # multiple target GGUFs in models/ (commit a87bb93 was a partial fix — + # the recurrence proved that "initialize once at the top of the block" + # is too easy to undo). Cost: zero bytes at runtime. + DRAFT_FAMILY_GLOB="${DRAFT_FAMILY_GLOB:-}" + if [ -n "$DRAFT_FILE" ] && [ -f "$DRAFT_FILE" ]; then + DRAFT_ARG="$DRAFT_FILE" + if [ -n "$DRAFT_FAMILY_GLOB" ]; then + info "Resolved draft dir $DFLASH_DRAFT → $DRAFT_ARG (target family: $DRAFT_FAMILY_GLOB)" + else + info "Resolved draft dir $DFLASH_DRAFT → $DRAFT_ARG" + fi + else + warn "No DFlash draft GGUF/safetensors in draft dir $DFLASH_DRAFT — running without draft" + DRAFT_ARG="" + fi +elif [ -n "$DFLASH_DRAFT" ] && [ ! -f "$DFLASH_DRAFT" ]; then + warn "Draft path $DFLASH_DRAFT not found — running without draft" + DRAFT_ARG="" +fi + +[ "$GPU_COUNT" -gt 1 ] && warn "${GPU_COUNT} GPUs detected — native server layer sharding is not auto-enabled" + +# ── build + exec native server ──────────────────────────────────────────── +CMD=("$DFLASH_SERVER_BIN" "$DFLASH_TARGET" + --host "$DFLASH_HOST" + --port "$DFLASH_PORT" + --max-ctx "$DFLASH_MAX_CTX" + --prefix-cache-slots "$DFLASH_PREFIX_CACHE_SLOTS" + --think-max-tokens "$DFLASH_THINK_MAX") + +[ -n "$DRAFT_ARG" ] && CMD+=(--draft "$DRAFT_ARG") +[ -n "$DRAFT_ARG" ] && CMD+=(--ddtree --ddtree-budget "$DFLASH_BUDGET") +# `--lazy-draft` is silently dropped by the C++ server unless both +# `--prefill-drafter` and `--draft` are present (look for the runtime +# warning `--lazy-draft ignored: requires both --prefill-drafter and +# --draft`). Warn loudly here when the operator's config asked for lazy +# but we're about to drop it — sweeping past the silent no-op was the +# fingerprint left in every sindri decode-tuning docker.stderr. +if [ "$DFLASH_LAZY" = "1" ]; then + if [ -z "$DRAFT_ARG" ] || [ -z "$DFLASH_PREFILL_DRAFTER" ]; then + warn "DFLASH_LAZY=1 ignored: requires both DFLASH_DRAFT and DFLASH_PREFILL_DRAFTER (see entrypoint.sh comment). Continuing without --lazy-draft." + else + CMD+=(--lazy-draft) + fi +fi +[ -n "$DFLASH_CACHE_TYPE_K" ] && CMD+=(--cache-type-k "$DFLASH_CACHE_TYPE_K") +[ -n "$DFLASH_CACHE_TYPE_V" ] && CMD+=(--cache-type-v "$DFLASH_CACHE_TYPE_V") +[ "$DFLASH_FA_WINDOW" -gt 0 ] 2>/dev/null && CMD+=(--fa-window "$DFLASH_FA_WINDOW") +# Soft-close ratio: emit only when nonzero. The default-string compare +# guards against the floating-point quirks of `[` numeric tests for +# values like 0.0/0/0.00 — anything non-"0.0" passes through to the +# server, which clamps to [0,1] itself. +case "$DFLASH_THINK_SOFT_CLOSE_MIN_RATIO" in + 0|0.0|0.00|0.000) ;; # disabled — don't emit + *) CMD+=(--think-soft-close-min-ratio "$DFLASH_THINK_SOFT_CLOSE_MIN_RATIO") ;; +esac +[ "$DFLASH_DEBUG_THINKING_LOGITS" = "1" ] && CMD+=(--debug-thinking-logits) + +if [ "$DFLASH_PREFILL_MODE" != "off" ]; then + [ -n "$DFLASH_PREFILL_DRAFTER" ] || die "DFLASH_PREFILL_MODE=$DFLASH_PREFILL_MODE requires DFLASH_PREFILL_DRAFTER" + [ -f "$DFLASH_PREFILL_DRAFTER" ] || die "Prefill drafter not found at $DFLASH_PREFILL_DRAFTER" + CMD+=(--prefill-compression "$DFLASH_PREFILL_MODE" + --prefill-keep-ratio "$DFLASH_PREFILL_KEEP" + --prefill-threshold "$DFLASH_PREFILL_THRESHOLD" + --prefill-drafter "$DFLASH_PREFILL_DRAFTER") +fi + +info "lucebox-hub container starting (target=$(basename "$DFLASH_TARGET"), max_ctx=$DFLASH_MAX_CTX, budget=$DFLASH_BUDGET, lazy=$DFLASH_LAZY)" + +cd "$DFLASH_DIR" +exec "${CMD[@]}" diff --git a/uv.lock b/uv.lock index 8830e0def..3f5f64aa9 100644 --- a/uv.lock +++ b/uv.lock @@ -17,11 +17,11 @@ members = [ [[package]] name = "aiohappyeyeballs" -version = "2.6.1" +version = "2.6.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/26/30/f84a107a9c4331c14b2b586036f40965c128aa4fee4dda5d3d51cb14ad54/aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558", size = 22760, upload-time = "2025-03-12T01:42:48.764Z" } +sdist = { url = "https://files.pythonhosted.org/packages/33/c6/61a2d7b7572279226bb2e7f61d7a19ca7c90da0329c93fa0d560cbf288d8/aiohappyeyeballs-2.6.2.tar.gz", hash = "sha256:e202810ee718bd01fc6ef49e8ea53d023d5cb6b581076d7925aa499fa55dbe64", size = 22591, upload-time = "2026-05-20T15:12:24.631Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/0f/15/5bf3b99495fb160b63f95972b81750f18f7f4e02ad051373b669d17d44f2/aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8", size = 15265, upload-time = "2025-03-12T01:42:47.083Z" }, + { url = "https://files.pythonhosted.org/packages/5f/fc/a7bf5b6e4e617b45f90f2d9d2a68519c249c81dd4fc2658c7a2a61c4f4b7/aiohappyeyeballs-2.6.2-py3-none-any.whl", hash = "sha256:4708045e2d7a6c6bdf8aafa8ed39649eaf926a4543b54560659129e3365953c4", size = 15062, upload-time = "2026-05-20T15:12:23.328Z" }, ] [[package]] @@ -104,11 +104,11 @@ wheels = [ [[package]] name = "certifi" -version = "2026.4.22" +version = "2026.5.20" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/25/ee/6caf7a40c36a1220410afe15a1cc64993a1f864871f698c0f93acb72842a/certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580", size = 137077, upload-time = "2026-04-22T11:26:11.191Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f3/ce/ee2ecad540810a79593028e88299baeae54d346cc7a0d94b6199988b89b1/certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d", size = 135422, upload-time = "2026-05-20T11:46:50.073Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/22/30/7cd8fdcdfbc5b869528b079bfb76dcdf6056b1a2097a662e5e8c04f42965/certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a", size = 135707, upload-time = "2026-04-22T11:26:09.372Z" }, + { url = "https://files.pythonhosted.org/packages/59/8c/57e832b7af6d7c5abe66eb3fbe3a3a32f4d11ea23a1aa7131371035be991/certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897", size = 134134, upload-time = "2026-05-20T11:46:48.578Z" }, ] [[package]] @@ -136,18 +136,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/db/8f/61959034484a4a7c527811f4721e75d02d653a35afb0b6054474d8185d4c/charset_normalizer-3.4.7-py3-none-any.whl", hash = "sha256:3dce51d0f5e7951f8bb4900c257dad282f49190fdbebecd4ba99bcc41fef404d", size = 61958, upload-time = "2026-04-02T09:28:37.794Z" }, ] -[[package]] -name = "click" -version = "8.3.3" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "colorama", marker = "sys_platform == 'win32'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/bb/63/f9e1ea081ce35720d8b92acde70daaedace594dc93b693c869e0d5910718/click-8.3.3.tar.gz", hash = "sha256:398329ad4837b2ff7cbe1dd166a4c0f8900c3ca3a218de04466f38f6497f18a2", size = 328061, upload-time = "2026-04-22T15:11:27.506Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ae/44/c1221527f6a71a01ec6fbad7fa78f1d50dfa02217385cf0fa3eec7087d59/click-8.3.3-py3-none-any.whl", hash = "sha256:a2bf429bb3033c89fa4936ffb35d5cb471e3719e1f3c8a7c3fff0b8314305613", size = 110502, upload-time = "2026-04-22T15:11:25.044Z" }, -] - [[package]] name = "colorama" version = "0.4.6" @@ -171,10 +159,10 @@ wheels = [ [[package]] name = "cuda-pathfinder" -version = "1.5.4" +version = "1.5.5" source = { registry = "https://pypi.org/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/11/d0/c177e29701cf1d3008d7d2b16b5fc626592ce13bd535f8795c5f57187e0e/cuda_pathfinder-1.5.4-py3-none-any.whl", hash = "sha256:9563d3175ce1828531acf4b94e1c1c7d67208c347ca002493e2654878b26f4b7", size = 51657, upload-time = "2026-04-27T22:42:07.712Z" }, + { url = "https://files.pythonhosted.org/packages/11/c8/26f2e4aae92f11522a96043892ba39a90eac610d5242523aa863212bc1c7/cuda_pathfinder-1.5.5-py3-none-any.whl", hash = "sha256:0228c023f95d1480f143ef5c8922d27a2ab052087a942e81dc289c9eb8f91689", size = 51671, upload-time = "2026-05-27T01:21:25.413Z" }, ] [[package]] @@ -372,7 +360,7 @@ wheels = [ [[package]] name = "huggingface-hub" -version = "1.14.0" +version = "1.16.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "filelock" }, @@ -385,18 +373,18 @@ dependencies = [ { name = "typer" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/39/40/43109e943fd718b0ccd0cd61eb4f1c347df22bf81f5874c6f22adf44bcff/huggingface_hub-1.14.0.tar.gz", hash = "sha256:d6d2c9cd6be1d02ae9ec6672d5587d10a427f377db688e82528f426a041622c2", size = 782365, upload-time = "2026-05-06T14:14:34.278Z" } +sdist = { url = "https://files.pythonhosted.org/packages/48/0f/ed994dbade67a54407c28cab96ef845e0e6d25500be56aca6394f8bfc9dd/huggingface_hub-1.16.1.tar.gz", hash = "sha256:7f1dc4c5ec21aed69be630ad0c3378616be16f3de1a47b141c0e812965d9c832", size = 792534, upload-time = "2026-05-21T18:40:00.908Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/89/a5/33b49ba7bea7c41bb37f74ec0f8beea0831e052330196633fe2c77516ea6/huggingface_hub-1.14.0-py3-none-any.whl", hash = "sha256:efe075535c62e130b30e836b138e13785f6f043d1f0539e0a39aa411a99e90b8", size = 661479, upload-time = "2026-05-06T14:14:32.029Z" }, + { url = "https://files.pythonhosted.org/packages/49/79/621a7dbb80c70974f73a597275351ebe03ce5bc65cb5f8f4acb5859252bc/huggingface_hub-1.16.1-py3-none-any.whl", hash = "sha256:64340de934b9ce37857ef85a82de72f5629e8a270f9119eabb12bf495eb53c22", size = 668176, upload-time = "2026-05-21T18:39:58.596Z" }, ] [[package]] name = "idna" -version = "3.15" +version = "3.16" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/82/77/7b3966d0b9d1d31a36ddf1746926a11dface89a83409bf1483f0237aa758/idna-3.15.tar.gz", hash = "sha256:ca962446ea538f7092a95e057da437618e886f4d349216d2b1e294abfdb65fdc", size = 199245, upload-time = "2026-05-12T22:45:57.011Z" } +sdist = { url = "https://files.pythonhosted.org/packages/1a/88/bcf9709822fe69d02c2a6a77956c98ce6ea8ca8767a9aadcedc7eb6a2390/idna-3.16.tar.gz", hash = "sha256:d7a6da03db833450fca25d2358ac9ff06cd624577a4aea3a596d5c0f77b8e03d", size = 203770, upload-time = "2026-05-22T00:16:18.781Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d2/23/408243171aa9aaba178d3e2559159c24c1171a641aa83b67bdd3394ead8e/idna-3.15-py3-none-any.whl", hash = "sha256:048adeaf8c2d788c40fee287673ccaa74c24ffd8dcf09ffa555a2fbb59f10ac8", size = 72340, upload-time = "2026-05-12T22:45:55.733Z" }, + { url = "https://files.pythonhosted.org/packages/94/16/70255075a9859a0e3adb789b68ceb0e210dec03934245fd98d248226572f/idna-3.16-py3-none-any.whl", hash = "sha256:cc246e3a3f89580c3a951b5ad298ca4638078b2cdd4f115654332b5c26daded5", size = 74165, upload-time = "2026-05-22T00:16:16.698Z" }, ] [[package]] @@ -420,6 +408,27 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" }, ] +[[package]] +name = "librt" +version = "0.11.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/40/08/9e7f6b5d2b5bed6ad055cdd5925f192bb403a51280f86b56554d9d0699a2/librt-0.11.0.tar.gz", hash = "sha256:075dc3ef4458a278e0195cbf6ac9d38808d9b906c5a6c7f7f79c3888276a3fb1", size = 200139, upload-time = "2026-05-10T18:17:25.138Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8b/d0/07c77e067f0838949b43bd89232c29d72efebb9d2801a9750184eb706b71/librt-0.11.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b87504f1690a23b9a2cca841191a04f83895d4fc2dd04df91d82b1a04ca2ad46", size = 144147, upload-time = "2026-05-10T18:15:53.227Z" }, + { url = "https://files.pythonhosted.org/packages/7a/24/8493538fa4f62f982686398a5b8f68008138a75086abdea19ade64bf4255/librt-0.11.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40071fc5fe0ce8daa6de616702314a01e1250711682b0523d6ab8d4525910cb3", size = 143614, upload-time = "2026-05-10T18:15:54.657Z" }, + { url = "https://files.pythonhosted.org/packages/ff/1e/f8bad050810d9171f34a1648ed910e56814c2ba61639f2bd53c6377ae24b/librt-0.11.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:137e79445c896a0ea7b265f52d23954e05b64222ee1af69e2cb34219067cbb67", size = 485538, upload-time = "2026-05-10T18:15:56.117Z" }, + { url = "https://files.pythonhosted.org/packages/c0/fe/3594ebfbaf03084ba4b120c9ba5c3183fd938a48725e9bbe6ff0a5159ad8/librt-0.11.0-cp312-cp312-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:cca6644054e78746d8d4ef238681f9c34ff8b584fe6b988ecebb8db3b15e622a", size = 479623, upload-time = "2026-05-10T18:15:57.544Z" }, + { url = "https://files.pythonhosted.org/packages/b0/da/5d1876984b3746c85dbd219dbfcb73c85f54ee263fd32e5b2a632ec14571/librt-0.11.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d5b0eea49f5562861ee8d757a32ef7d559c1d35be2aaaa1ec28941d74c9ffc8a", size = 513082, upload-time = "2026-05-10T18:15:58.805Z" }, + { url = "https://files.pythonhosted.org/packages/19/6e/55bdf5d5ca00c3e18430690bf2c953d8d3ffd3c337418173d33dec985dc9/librt-0.11.0-cp312-cp312-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0d1029d7e1ae1a7e647ed6fb5df8c4ce2dffefb7a9f5fd1376a4554d96dac09f", size = 508105, upload-time = "2026-05-10T18:16:00.2Z" }, + { url = "https://files.pythonhosted.org/packages/07/10/f1f23a7c595ee90ece4d35c851e5d104b1311a887ed1b4ac4c35bbd13da8/librt-0.11.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:bc3ce6b33c5828d9e80592011a5c584cb2ce86edbc4088405f70da47dc1d1b3b", size = 522268, upload-time = "2026-05-10T18:16:01.708Z" }, + { url = "https://files.pythonhosted.org/packages/b6/02/5720f5697a7f54b78b3aefbe20df3a48cedcff1276618c4aa481177942ed/librt-0.11.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:936c5995f3514a42111f20099397d8177c79b4d7e70961e396c6f5a0a3566766", size = 527348, upload-time = "2026-05-10T18:16:03.496Z" }, + { url = "https://files.pythonhosted.org/packages/50/db/b4a47c6f91db4ff76348a0b3dd0cc65e090a078b765a810a62ff9434c3d3/librt-0.11.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:9bc0ca6ad9381cbe8e4aa6e5726e4c80c78115a6e9723c599ed1d73e092bc49d", size = 516294, upload-time = "2026-05-10T18:16:05.173Z" }, + { url = "https://files.pythonhosted.org/packages/9e/58/9384b2f4eb1ed1d273d40948a7c5c4b2360213b402ef3be4641c06299f9c/librt-0.11.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:070aa8c26c0a74774317a72df8851facc7f0f012a5b406557ac56992d92e1ec8", size = 553608, upload-time = "2026-05-10T18:16:06.839Z" }, + { url = "https://files.pythonhosted.org/packages/21/7b/5aa8848a7c6a9278c79375146da1812e695754ceec5f005e6043461a7315/librt-0.11.0-cp312-cp312-win32.whl", hash = "sha256:6bf14feb84b05ae945277395451998c89c54d0def4070eb5c08de544930b245a", size = 101879, upload-time = "2026-05-10T18:16:08.103Z" }, + { url = "https://files.pythonhosted.org/packages/37/33/8a745436944947575b584231750a41417de1a38cf6a2e9251d1065651c09/librt-0.11.0-cp312-cp312-win_amd64.whl", hash = "sha256:75672f0bc524ede266287d532d7923dbce94c7514ad07627bac3d0c6d92cc4d9", size = 119831, upload-time = "2026-05-10T18:16:09.174Z" }, + { url = "https://files.pythonhosted.org/packages/59/67/a6739ac96e28b7855808bdb0370e250606104a859750d209e5a0716fe7ab/librt-0.11.0-cp312-cp312-win_arm64.whl", hash = "sha256:2f10cf143e4a9bb0f4f5af568a00df94a2d69ef41c2579584454bb0fe5cc642c", size = 103470, upload-time = "2026-05-10T18:16:10.369Z" }, +] + [[package]] name = "lucebox-dflash" version = "0.1.0" @@ -463,21 +472,33 @@ dependencies = [ [package.optional-dependencies] dev = [ + { name = "mypy" }, { name = "pytest" }, + { name = "ruff" }, ] megakernel = [ { name = "qwen35-megakernel-bf16" }, ] +[package.dev-dependencies] +dev = [ + { name = "pytest" }, +] + [package.metadata] requires-dist = [ { name = "lucebox-dflash", virtual = "server" }, + { name = "mypy", marker = "extra == 'dev'", specifier = ">=1.10,<2" }, { name = "pflash", editable = "optimizations/pflash" }, { name = "pytest", marker = "extra == 'dev'", specifier = ">=8" }, { name = "qwen35-megakernel-bf16", marker = "extra == 'megakernel'", editable = "optimizations/megakernel" }, + { name = "ruff", marker = "extra == 'dev'", specifier = ">=0.14,<1" }, ] provides-extras = ["megakernel", "dev"] +[package.metadata.requires-dev] +dev = [{ name = "pytest", specifier = ">=9.0.3" }] + [[package]] name = "markdown-it-py" version = "4.2.0" @@ -569,6 +590,37 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/7e/82/69e539c4c2027f1e1697e09aaa2449243085a0edf81ae2c6341e84d769b6/multiprocess-0.70.19-py39-none-any.whl", hash = "sha256:0d4b4397ed669d371c81dcd1ef33fd384a44d6c3de1bd0ca7ac06d837720d3c5", size = 133477, upload-time = "2026-01-19T06:47:38.619Z" }, ] +[[package]] +name = "mypy" +version = "1.20.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "librt", marker = "platform_python_implementation != 'PyPy'" }, + { name = "mypy-extensions" }, + { name = "pathspec" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/04/af/e3d4b3e9ec91a0ff9aabfdb38692952acf49bbb899c2e4c29acb3a6da3ae/mypy-1.20.2.tar.gz", hash = "sha256:e8222c26daaafd9e8626dec58ae36029f82585890589576f769a650dd20fd665", size = 3817349, upload-time = "2026-04-21T17:12:28.473Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/71/4e/7560e4528db9e9b147e4c0f22660466bf30a0a1fe3d63d1b9d3b0fd354ee/mypy-1.20.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4dbfcf869f6b0517f70cf0030ba6ea1d6645e132337a7d5204a18d8d5636c02b", size = 14539393, upload-time = "2026-04-21T17:07:12.52Z" }, + { url = "https://files.pythonhosted.org/packages/32/d9/34a5efed8124f5a9234f55ac6a4ced4201e2c5b81e1109c49ad23190ec8c/mypy-1.20.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4b6481b228d072315b053210b01ac320e1be243dc17f9e5887ef167f23f5fae4", size = 13361642, upload-time = "2026-04-21T17:06:53.742Z" }, + { url = "https://files.pythonhosted.org/packages/d1/14/eb377acf78c03c92d566a1510cda8137348215b5335085ef662ab82ecd3a/mypy-1.20.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:34397cdced6b90b836e38182076049fdb41424322e0b0728c946b0939ebdf9f6", size = 13740347, upload-time = "2026-04-21T17:12:04.73Z" }, + { url = "https://files.pythonhosted.org/packages/b9/94/7e4634a32b641aa1c112422eed1bbece61ee16205f674190e8b536f884de/mypy-1.20.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a5da6976f20cae27059ea8d0c86e7cef3de720e04c4bb9ee18e3690fdb792066", size = 14734042, upload-time = "2026-04-21T17:07:43.16Z" }, + { url = "https://files.pythonhosted.org/packages/7a/f3/f7e62395cb7f434541b4491a01149a4439e28ace4c0c632bbf5431e92d1f/mypy-1.20.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:56908d7e08318d39f85b1f0c6cfd47b0cac1a130da677630dac0de3e0623e102", size = 14964958, upload-time = "2026-04-21T17:11:00.665Z" }, + { url = "https://files.pythonhosted.org/packages/3e/0d/47e3c3a0ec2a876e35aeac365df3cac7776c36bbd4ed18cc521e1b9d255b/mypy-1.20.2-cp312-cp312-win_amd64.whl", hash = "sha256:d52ad8d78522da1d308789df651ee5379088e77c76cb1994858d40a426b343b9", size = 10911340, upload-time = "2026-04-21T17:10:49.179Z" }, + { url = "https://files.pythonhosted.org/packages/d6/b2/6c852d72e0ea8b01f49da817fb52539993cde327e7d010e0103dc12d0dac/mypy-1.20.2-cp312-cp312-win_arm64.whl", hash = "sha256:785b08db19c9f214dc37d65f7c165d19a30fcecb48abfa30f31b01b5acaabb58", size = 9833947, upload-time = "2026-04-21T17:09:05.267Z" }, + { url = "https://files.pythonhosted.org/packages/28/9a/f23c163e25b11074188251b0b5a0342625fc1cdb6af604757174fa9acc9b/mypy-1.20.2-py3-none-any.whl", hash = "sha256:a94c5a76ab46c5e6257c7972b6c8cff0574201ca7dc05647e33e795d78680563", size = 2637314, upload-time = "2026-04-21T17:05:54.5Z" }, +] + +[[package]] +name = "mypy-extensions" +version = "1.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/6e/371856a3fb9d31ca8dac321cda606860fa4548858c0cc45d9d1d4ca2628b/mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558", size = 6343, upload-time = "2025-04-22T14:54:24.164Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", size = 4963, upload-time = "2025-04-22T14:54:22.983Z" }, +] + [[package]] name = "networkx" version = "3.6.1" @@ -580,21 +632,21 @@ wheels = [ [[package]] name = "numpy" -version = "2.4.4" +version = "2.4.6" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d7/9f/b8cef5bffa569759033adda9481211426f12f53299629b410340795c2514/numpy-2.4.4.tar.gz", hash = "sha256:2d390634c5182175533585cc89f3608a4682ccb173cc9bb940b2881c8d6f8fa0", size = 20731587, upload-time = "2026-03-29T13:22:01.298Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d0/ad/fed0499ce6a338d2a03ebae59cd15093910c8875328855781952abf6c2fe/numpy-2.4.6.tar.gz", hash = "sha256:f3a3570c4a2a16746ac2c31a7c7c7b0c186b95ce902e33db6f28094ed7387dda", size = 20735807, upload-time = "2026-05-18T23:37:14.07Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/28/05/32396bec30fb2263770ee910142f49c1476d08e8ad41abf8403806b520ce/numpy-2.4.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:15716cfef24d3a9762e3acdf87e27f58dc823d1348f765bbea6bef8c639bfa1b", size = 16689272, upload-time = "2026-03-29T13:18:49.223Z" }, - { url = "https://files.pythonhosted.org/packages/c5/f3/a983d28637bfcd763a9c7aafdb6d5c0ebf3d487d1e1459ffdb57e2f01117/numpy-2.4.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:23cbfd4c17357c81021f21540da84ee282b9c8fba38a03b7b9d09ba6b951421e", size = 14699573, upload-time = "2026-03-29T13:18:52.629Z" }, - { url = "https://files.pythonhosted.org/packages/9b/fd/e5ecca1e78c05106d98028114f5c00d3eddb41207686b2b7de3e477b0e22/numpy-2.4.4-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:8b3b60bb7cba2c8c81837661c488637eee696f59a877788a396d33150c35d842", size = 5204782, upload-time = "2026-03-29T13:18:55.579Z" }, - { url = "https://files.pythonhosted.org/packages/de/2f/702a4594413c1a8632092beae8aba00f1d67947389369b3777aed783fdca/numpy-2.4.4-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:e4a010c27ff6f210ff4c6ef34394cd61470d01014439b192ec22552ee867f2a8", size = 6552038, upload-time = "2026-03-29T13:18:57.769Z" }, - { url = "https://files.pythonhosted.org/packages/7f/37/eed308a8f56cba4d1fdf467a4fc67ef4ff4bf1c888f5fc980481890104b1/numpy-2.4.4-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f9e75681b59ddaa5e659898085ae0eaea229d054f2ac0c7e563a62205a700121", size = 15670666, upload-time = "2026-03-29T13:19:00.341Z" }, - { url = "https://files.pythonhosted.org/packages/0a/0d/0e3ecece05b7a7e87ab9fb587855548da437a061326fff64a223b6dcb78a/numpy-2.4.4-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:81f4a14bee47aec54f883e0cad2d73986640c1590eb9bfaaba7ad17394481e6e", size = 16645480, upload-time = "2026-03-29T13:19:03.63Z" }, - { url = "https://files.pythonhosted.org/packages/34/49/f2312c154b82a286758ee2f1743336d50651f8b5195db18cdb63675ff649/numpy-2.4.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:62d6b0f03b694173f9fcb1fb317f7222fd0b0b103e784c6549f5e53a27718c44", size = 17020036, upload-time = "2026-03-29T13:19:07.428Z" }, - { url = "https://files.pythonhosted.org/packages/7b/e9/736d17bd77f1b0ec4f9901aaec129c00d59f5d84d5e79bba540ef12c2330/numpy-2.4.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fbc356aae7adf9e6336d336b9c8111d390a05df88f1805573ebb0807bd06fd1d", size = 18368643, upload-time = "2026-03-29T13:19:10.775Z" }, - { url = "https://files.pythonhosted.org/packages/63/f6/d417977c5f519b17c8a5c3bc9e8304b0908b0e21136fe43bf628a1343914/numpy-2.4.4-cp312-cp312-win32.whl", hash = "sha256:0d35aea54ad1d420c812bfa0385c71cd7cc5bcf7c65fed95fc2cd02fe8c79827", size = 5961117, upload-time = "2026-03-29T13:19:13.464Z" }, - { url = "https://files.pythonhosted.org/packages/2d/5b/e1deebf88ff431b01b7406ca3583ab2bbb90972bbe1c568732e49c844f7e/numpy-2.4.4-cp312-cp312-win_amd64.whl", hash = "sha256:b5f0362dc928a6ecd9db58868fca5e48485205e3855957bdedea308f8672ea4a", size = 12320584, upload-time = "2026-03-29T13:19:16.155Z" }, - { url = "https://files.pythonhosted.org/packages/58/89/e4e856ac82a68c3ed64486a544977d0e7bdd18b8da75b78a577ca31c4395/numpy-2.4.4-cp312-cp312-win_arm64.whl", hash = "sha256:846300f379b5b12cc769334464656bc882e0735d27d9726568bc932fdc49d5ec", size = 10221450, upload-time = "2026-03-29T13:19:18.994Z" }, + { url = "https://files.pythonhosted.org/packages/95/2a/3d7b5ac8aac24feaf9ad7ed58f45b0bbc06d37e4338ae84c9f2298b570f9/numpy-2.4.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:001fbb8e08d942dd57599e781f2472269ee7f2755fae407b4f67b2f0b17da3f1", size = 16689119, upload-time = "2026-05-18T23:33:54.065Z" }, + { url = "https://files.pythonhosted.org/packages/ea/12/92c4c131527599e8288d6918e888d88726f84d805d784b771f32408aeaef/numpy-2.4.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ebfb099f8dcf083deef3ac1ca4c1503f387cf76296fcb3816b66f5ecb5f54fdb", size = 14699246, upload-time = "2026-05-18T23:33:57.621Z" }, + { url = "https://files.pythonhosted.org/packages/ad/fe/c0a6b7b2ca128a8fb228575147073b660656734b8ebe4d76c8fd748dcc79/numpy-2.4.6-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:3213d622a0283a39a93d188f3cf72b26862df52fbb4ca3697f51705016523d41", size = 5204410, upload-time = "2026-05-18T23:34:00.302Z" }, + { url = "https://files.pythonhosted.org/packages/f3/d4/9770d14ba719432bb90a421bfd443872ed0f70f7264b64bec12ea363d5fd/numpy-2.4.6-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:357cc07a6d7b0b182ff02249616a03742827ebb1277546b5c7cd7f7620a45698", size = 6551240, upload-time = "2026-05-18T23:34:02.852Z" }, + { url = "https://files.pythonhosted.org/packages/c9/c6/50a46a6205feba2343f1d6d17438107c5dc491ed1c736e6ea68689fd906b/numpy-2.4.6-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5f9fb9157b4ce2971008323afe46053787b526ef624fea915b261468a8421a0f", size = 15671012, upload-time = "2026-05-18T23:34:05.485Z" }, + { url = "https://files.pythonhosted.org/packages/99/60/14115e6364fa676c5397c2ad3004e527e9aa487abf5d0706ec81bbd08529/numpy-2.4.6-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:90f9849678c75fe7afa2d348ac842c168b0a4d3d61919687216dfc547976d853", size = 16645538, upload-time = "2026-05-18T23:34:09.265Z" }, + { url = "https://files.pythonhosted.org/packages/ae/c5/693cbe59e57db94d2231fa519ca3978dc9e19da5a8f088588f5c6e947ff2/numpy-2.4.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:c1a2af6c6ef86344a6b0db6b97834208bf598db514f2b155042439b62605601a", size = 17020706, upload-time = "2026-05-18T23:34:13.053Z" }, + { url = "https://files.pythonhosted.org/packages/ef/fc/85b7c4eff9b4966ade25c2273cf7e7012e92366c032058653934b37de044/numpy-2.4.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e5805d5a22fd19c8ccff10a9561f9df94436b0545619ea579db2d3c35294bce2", size = 18368541, upload-time = "2026-05-18T23:34:17.024Z" }, + { url = "https://files.pythonhosted.org/packages/f6/81/e1b27545deedce7f4a0b348618c6b62d74e36a4dc9ccd42f3eb2f85eee32/numpy-2.4.6-cp312-cp312-win32.whl", hash = "sha256:e3eeb0aabd6bd5ce64faae67e9935203a6991b4bc2a485a767fbafb2c5125f45", size = 5962825, upload-time = "2026-05-18T23:34:20.3Z" }, + { url = "https://files.pythonhosted.org/packages/ab/ca/feab00bd44aa5fe1ad2c18f08b4d3bb92e26484b0b1d1443897809ed528c/numpy-2.4.6-cp312-cp312-win_amd64.whl", hash = "sha256:d8e8286dd7cea7895157318d1b91cdacac64c479f3cbc8dce548331728484751", size = 12321687, upload-time = "2026-05-18T23:34:23.095Z" }, + { url = "https://files.pythonhosted.org/packages/63/cf/5a6d34850a39d1093558564f77ee8e8e0bee5061151b8f05a55711001ec7/numpy-2.4.6-cp312-cp312-win_arm64.whl", hash = "sha256:4081eb135ac24158bd51cdfbef16f1c64df7063b1143f24731387137c092bec8", size = 10221482, upload-time = "2026-05-18T23:34:25.876Z" }, ] [[package]] @@ -776,6 +828,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/86/bd/fda8f9705b1b09c6ebe14bfc0fa0e4ec8584d54ea673628f157ff55131af/pandas-3.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:557409bc4178e70ee8d9ddb494798e51ebf6ea59330f6be22c51bab2a7db6c49", size = 9066158, upload-time = "2026-05-11T18:52:56.038Z" }, ] +[[package]] +name = "pathspec" +version = "1.1.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5a/82/42f767fc1c1143d6fd36efb827202a2d997a375e160a71eb2888a925aac1/pathspec-1.1.1.tar.gz", hash = "sha256:17db5ecd524104a120e173814c90367a96a98d07c45b2e10c2f3919fff91bf5a", size = 135180, upload-time = "2026-04-27T01:46:08.907Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f1/d9/7fb5aa316bc299258e68c73ba3bddbc499654a07f151cba08f6153988714/pathspec-1.1.1-py3-none-any.whl", hash = "sha256:a00ce642f577bf7f473932318056212bc4f8bfdf53128c78bbd5af0b9b20b189", size = 57328, upload-time = "2026-04-27T01:46:07.06Z" }, +] + [[package]] name = "pflash" version = "0.3.0" @@ -933,7 +994,7 @@ wheels = [ [[package]] name = "requests" -version = "2.34.1" +version = "2.34.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "certifi" }, @@ -941,9 +1002,9 @@ dependencies = [ { name = "idna" }, { name = "urllib3" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/24/36/7180e7f077c38108945dbbdf60fe04db681c3feb6e96419f8c6dc8723741/requests-2.34.1.tar.gz", hash = "sha256:0fc5669f2b69704449fe1552360bd2a73a54512dfd03e65529157f1513322beb", size = 142783, upload-time = "2026-05-13T19:20:24.662Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ac/c3/e2a2b89f2d3e2179abd6d00ebd70bff6273f37fb3e0cc209f48b39d00cbf/requests-2.34.2.tar.gz", hash = "sha256:f288924cae4e29463698d6d60bc6a4da69c89185ad1e0bcc4104f584e960b9ed", size = 142856, upload-time = "2026-05-14T19:25:27.735Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/15/5a/4a949d170476de3c04ac036b5466422fbcbf348a917d8042eedf2cac7d1b/requests-2.34.1-py3-none-any.whl", hash = "sha256:bf38a3ff993960d3dd819c08862c40b3c703306eb7c744fcd9f4ddbb95b548f0", size = 73085, upload-time = "2026-05-13T19:20:22.827Z" }, + { url = "https://files.pythonhosted.org/packages/a0/f4/c67b0b3f1b9245e8d266f0f112c500d50e5b4e83cb6f3b71b6528104182a/requests-2.34.2-py3-none-any.whl", hash = "sha256:2a0d60c172f83ac6ab31e4554906c0f3b3588d37b5cb939b1c061f4907e278e0", size = 73075, upload-time = "2026-05-14T19:25:26.443Z" }, ] [[package]] @@ -959,6 +1020,31 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/82/3b/64d4899d73f91ba49a8c18a8ff3f0ea8f1c1d75481760df8c68ef5235bf5/rich-15.0.0-py3-none-any.whl", hash = "sha256:33bd4ef74232fb73fe9279a257718407f169c09b78a87ad3d296f548e27de0bb", size = 310654, upload-time = "2026-04-12T08:24:02.83Z" }, ] +[[package]] +name = "ruff" +version = "0.15.14" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/dc/8a/8bce2894573e9dae6ff4d77fe34ad727d79b9e6238ad288c5638990d90f6/ruff-0.15.14.tar.gz", hash = "sha256:48e866b165be4a9bdbf310f7d3c9a07edef2fe8cd63ffeb4e00bb590506ebf9f", size = 4700910, upload-time = "2026-05-21T14:34:55.177Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b9/c8/74a92c6ff9fcfb4f1f947126d3ebee8389276e161ecc85de5bda7cda51bd/ruff-0.15.14-py3-none-linux_armv6l.whl", hash = "sha256:8dd2db9416e487c8d4b01fa7056bb02c4d05969d4f8d17a08c229c2f4ff3c108", size = 10739177, upload-time = "2026-05-21T14:34:37.332Z" }, + { url = "https://files.pythonhosted.org/packages/45/91/254a35c20acc38a7223c9d2d594af12e794432464f2cdeb52af1dc4a892d/ruff-0.15.14-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:be4ff55af755bd71a00ab3dc6bd7ffc467bd76e0df6881e286c2e3d23e8fb43b", size = 11144969, upload-time = "2026-05-21T14:34:43.978Z" }, + { url = "https://files.pythonhosted.org/packages/56/9e/d13e40f83b8d0a94430e6778ce1d94a43b38cf2efe63278bdd2b4c65abbf/ruff-0.15.14-py3-none-macosx_11_0_arm64.whl", hash = "sha256:48d5909d7d06276ce7dde6d32bfa4b0d4cb2651145cd8ee4b440722cbc77832f", size = 10478207, upload-time = "2026-05-21T14:34:48.378Z" }, + { url = "https://files.pythonhosted.org/packages/8d/f1/b15a7839fa4f332f8acec78e20564f26bb2d866e3d21710b877fd0263000/ruff-0.15.14-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca8cbfa94c4f90984a67561978602746d4cd27103568f745fa90eee3f0d4107d", size = 10818459, upload-time = "2026-05-21T14:34:22.318Z" }, + { url = "https://files.pythonhosted.org/packages/45/33/53d651177f84f94b400a0e27f8824eeada3dddc9d5ee8aeb048f4352a520/ruff-0.15.14-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9a6bbc0333f1ab053423bcbf6226477d266ca7cec7738c4c8e3f55647803f3c4", size = 10541800, upload-time = "2026-05-21T14:34:20.209Z" }, + { url = "https://files.pythonhosted.org/packages/b8/a6/868f87e0bf9786ed24b5d0d0ad8676b8a94fd1912f42cddf9cfc7857818a/ruff-0.15.14-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8a24a4f7605d7003a6674d4387651effd939dead3fddd0f36561eb77a9a2e542", size = 11342149, upload-time = "2026-05-21T14:34:46.365Z" }, + { url = "https://files.pythonhosted.org/packages/a7/8b/38cd5c19faffdcc05a408d2b78edccc69492ab9720eadb49ea15ef80d768/ruff-0.15.14-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:049b5326e53ed80978f2fc041a280603f69dd6b0c95464342a2bb4572d9d9e2f", size = 12212563, upload-time = "2026-05-21T14:34:28.579Z" }, + { url = "https://files.pythonhosted.org/packages/3e/4d/a3c5b874a556d5731e3e657aaf04311bb76f0a5c3ec220ed43051be6b64b/ruff-0.15.14-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d4ed42e6696c8dfa5f06728e6441993901f548eb92d73bc472cb5a38d1395fbf", size = 11493299, upload-time = "2026-05-21T14:34:41.836Z" }, + { url = "https://files.pythonhosted.org/packages/1e/c0/56472c251d09858a53e51efbd485b09e1995d8731668b76d52e5dd6ee0f1/ruff-0.15.14-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:715c543cf450c4888251f91c52f1942a800541d9bddd7ac060aa4e6b77ae7cba", size = 11455931, upload-time = "2026-05-21T14:34:57.276Z" }, + { url = "https://files.pythonhosted.org/packages/2c/4a/e2e7b4d8dbf233d4eace59c75bc3435fa6d8bd3bae82d351d4e4300c0fd1/ruff-0.15.14-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:72ebab6013ec887d439d8b7593737a0a4ffb06d45d209d4e4bf2e92813082d3f", size = 11400794, upload-time = "2026-05-21T14:34:39.773Z" }, + { url = "https://files.pythonhosted.org/packages/97/c7/83c0539fe34c3e09136204d1e75d6052492364e0b3cb05e9465423f567d7/ruff-0.15.14-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:49072d36abdbe97a8dd7f480afe9c675699c0c495d4c84076e2c1203c4550581", size = 10804759, upload-time = "2026-05-21T14:34:31.045Z" }, + { url = "https://files.pythonhosted.org/packages/86/a6/18f2bfc095a2ab4a78745644e428205532ce6653a5d0fa8501572891534d/ruff-0.15.14-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:958522aee105068640c2c2ceae08f413ae44d922f52a1374ac13d6a96032fc93", size = 10539517, upload-time = "2026-05-21T14:34:53.064Z" }, + { url = "https://files.pythonhosted.org/packages/54/3a/5a8b3b69c654d4e4bf1d246ac5b49cbcdac6eaab6905925f8915f31e3b80/ruff-0.15.14-py3-none-musllinux_1_2_i686.whl", hash = "sha256:f3707da619a143a2e8830e2abab8224478d69ace2d28cb6c20543ae97c36bf61", size = 11065169, upload-time = "2026-05-21T14:34:24.484Z" }, + { url = "https://files.pythonhosted.org/packages/ed/c5/8864e4e7925b836ea354b31d57641ec03830564e281a8b6f061f8c3e0ec1/ruff-0.15.14-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:bb01d645694e3ec0102105d07ef2d53703970407d59c04e59d3ba0b7a1d53553", size = 11560214, upload-time = "2026-05-21T14:34:50.975Z" }, + { url = "https://files.pythonhosted.org/packages/36/38/012bf76752e1f89ed50b77b99532d90f3a3e287bc7918e1fc0948ac866ac/ruff-0.15.14-py3-none-win32.whl", hash = "sha256:6d0c1ad2a0ab718d39b6d8fd2217981ce4d625cd96a720095f798fb47d8b13e6", size = 10805548, upload-time = "2026-05-21T14:34:33.453Z" }, + { url = "https://files.pythonhosted.org/packages/d1/b7/4ea2c170f10ad760fff2a5250beb18897719dc8b52b53a24cddbb9dd3f19/ruff-0.15.14-py3-none-win_amd64.whl", hash = "sha256:802342981e056db3851a7836e5b070f8f15f67d4a685ae2a6160939d364b2902", size = 11939523, upload-time = "2026-05-21T14:34:18.077Z" }, + { url = "https://files.pythonhosted.org/packages/62/d5/bc97ff895ec35cf3925d4bd60f3b39d822f377a446906ec9bcc87405e59b/ruff-0.15.14-py3-none-win_arm64.whl", hash = "sha256:ff47b90a9ef6a40c9e2f3b479c1fb78531adf055b94c1eba0a7ba04b31951826", size = 11208607, upload-time = "2026-05-21T14:34:26.525Z" }, +] + [[package]] name = "safetensors" version = "0.7.0" @@ -1086,7 +1172,7 @@ wheels = [ [[package]] name = "transformers" -version = "5.8.1" +version = "5.9.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "huggingface-hub" }, @@ -1099,9 +1185,9 @@ dependencies = [ { name = "tqdm" }, { name = "typer" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/e7/e6/4134ea2fbea322cddc7ffc94a0d8ee47fe32ce8e876b320cd37d88edfc4d/transformers-5.8.1.tar.gz", hash = "sha256:4dd5b6de4105725104d84fd6abd74b305f4debfc251b38c648ee5dd087cf543b", size = 8532019, upload-time = "2026-05-13T03:21:57.234Z" } +sdist = { url = "https://files.pythonhosted.org/packages/51/58/7f843608f2e8421f86bb97060b54649be6239ec612b82bf9d41e65c26c00/transformers-5.9.0.tar.gz", hash = "sha256:25997cb8fa6053533171634b6162d7df54346530ec2aa9b42bb834e63668c842", size = 8642240, upload-time = "2026-05-20T14:50:49.278Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/fc/b1/8be7e7ef0b5200491312201918b6125ef9c9df9dd0f0240ccef9ac824e6b/transformers-5.8.1-py3-none-any.whl", hash = "sha256:5340fb95962162cdfdae5cc91d7f8fedd92ed75216c1154c5e1f590fcf56dd0e", size = 10632882, upload-time = "2026-05-13T03:21:52.876Z" }, + { url = "https://files.pythonhosted.org/packages/02/ca/2eaa5359f2ccb8c2e1656bc26305ad0cf438aa392ce4b29ae67a315c186e/transformers-5.9.0-py3-none-any.whl", hash = "sha256:1d19509bcff7028ebc6b277d71caa712e8353778463d38764237d14b42b52788", size = 10787648, upload-time = "2026-05-20T14:50:45.337Z" }, ] [[package]] @@ -1115,17 +1201,17 @@ wheels = [ [[package]] name = "typer" -version = "0.25.1" +version = "0.26.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "annotated-doc" }, - { name = "click" }, + { name = "colorama", marker = "sys_platform == 'win32'" }, { name = "rich" }, { name = "shellingham" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/e4/51/9aed62104cea109b820bbd6c14245af756112017d309da813ef107d42e7e/typer-0.25.1.tar.gz", hash = "sha256:9616eb8853a09ffeabab1698952f33c6f29ffdbceb4eaeecf571880e8d7664cc", size = 122276, upload-time = "2026-04-30T19:32:16.964Z" } +sdist = { url = "https://files.pythonhosted.org/packages/67/a5/756f2e6bc81a7dd79aa3c625dd01b74cabc4516628cace2caaec09ca6ff2/typer-0.26.2.tar.gz", hash = "sha256:9b4f19e08fcc9427a822d1ef467b1fe76737a2f65c7926bdeba2337d73569b68", size = 198991, upload-time = "2026-05-27T10:41:39.166Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/3f/f9/2b3ff4e56e5fa7debfaf9eb135d0da96f3e9a1d5b27222223c7296336e5f/typer-0.25.1-py3-none-any.whl", hash = "sha256:75caa44ed46a03fb2dab8808753ffacdbfea88495e74c85a28c5eefcf5f39c89", size = 58409, upload-time = "2026-04-30T19:32:18.271Z" }, + { url = "https://files.pythonhosted.org/packages/8b/a5/6ffd702beda8798b2b82ff70805ed4a66d963557e43a5d1823ab456251a4/typer-0.26.2-py3-none-any.whl", hash = "sha256:39beff72ffbb31978a5b545f677d57edb97c6f980f433b38556deb0af25f094d", size = 123123, upload-time = "2026-05-27T10:41:40.504Z" }, ] [[package]] @@ -1184,32 +1270,31 @@ wheels = [ [[package]] name = "yarl" -version = "1.23.0" +version = "1.24.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "idna" }, { name = "multidict" }, { name = "propcache" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/23/6e/beb1beec874a72f23815c1434518bfc4ed2175065173fb138c3705f658d4/yarl-1.23.0.tar.gz", hash = "sha256:53b1ea6ca88ebd4420379c330aea57e258408dd0df9af0992e5de2078dc9f5d5", size = 194676, upload-time = "2026-03-01T22:07:53.373Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/88/8a/94615bc31022f711add374097ad4144d569e95ff3c38d39215d07ac153a0/yarl-1.23.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:1932b6b8bba8d0160a9d1078aae5838a66039e8832d41d2992daa9a3a08f7860", size = 124737, upload-time = "2026-03-01T22:05:12.897Z" }, - { url = "https://files.pythonhosted.org/packages/e3/6f/c6554045d59d64052698add01226bc867b52fe4a12373415d7991fdca95d/yarl-1.23.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:411225bae281f114067578891bc75534cfb3d92a3b4dfef7a6ca78ba354e6069", size = 87029, upload-time = "2026-03-01T22:05:14.376Z" }, - { url = "https://files.pythonhosted.org/packages/19/2a/725ecc166d53438bc88f76822ed4b1e3b10756e790bafd7b523fe97c322d/yarl-1.23.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:13a563739ae600a631c36ce096615fe307f131344588b0bc0daec108cdb47b25", size = 86310, upload-time = "2026-03-01T22:05:15.71Z" }, - { url = "https://files.pythonhosted.org/packages/99/30/58260ed98e6ff7f90ba84442c1ddd758c9170d70327394a6227b310cd60f/yarl-1.23.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9cbf44c5cb4a7633d078788e1b56387e3d3cf2b8139a3be38040b22d6c3221c8", size = 97587, upload-time = "2026-03-01T22:05:17.384Z" }, - { url = "https://files.pythonhosted.org/packages/76/0a/8b08aac08b50682e65759f7f8dde98ae8168f72487e7357a5d684c581ef9/yarl-1.23.0-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:53ad387048f6f09a8969631e4de3f1bf70c50e93545d64af4f751b2498755072", size = 92528, upload-time = "2026-03-01T22:05:18.804Z" }, - { url = "https://files.pythonhosted.org/packages/52/07/0b7179101fe5f8385ec6c6bb5d0cb9f76bd9fb4a769591ab6fb5cdbfc69a/yarl-1.23.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4a59ba56f340334766f3a4442e0efd0af895fae9e2b204741ef885c446b3a1a8", size = 105339, upload-time = "2026-03-01T22:05:20.235Z" }, - { url = "https://files.pythonhosted.org/packages/d3/8a/36d82869ab5ec829ca8574dfcb92b51286fcfb1e9c7a73659616362dc880/yarl-1.23.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:803a3c3ce4acc62eaf01eaca1208dcf0783025ef27572c3336502b9c232005e7", size = 105061, upload-time = "2026-03-01T22:05:22.268Z" }, - { url = "https://files.pythonhosted.org/packages/66/3e/868e5c3364b6cee19ff3e1a122194fa4ce51def02c61023970442162859e/yarl-1.23.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a3d2bff8f37f8d0f96c7ec554d16945050d54462d6e95414babaa18bfafc7f51", size = 100132, upload-time = "2026-03-01T22:05:23.638Z" }, - { url = "https://files.pythonhosted.org/packages/cf/26/9c89acf82f08a52cb52d6d39454f8d18af15f9d386a23795389d1d423823/yarl-1.23.0-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c75eb09e8d55bceb4367e83496ff8ef2bc7ea6960efb38e978e8073ea59ecb67", size = 99289, upload-time = "2026-03-01T22:05:25.749Z" }, - { url = "https://files.pythonhosted.org/packages/6f/54/5b0db00d2cb056922356104468019c0a132e89c8d3ab67d8ede9f4483d2a/yarl-1.23.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:877b0738624280e34c55680d6054a307aa94f7d52fa0e3034a9cc6e790871da7", size = 96950, upload-time = "2026-03-01T22:05:27.318Z" }, - { url = "https://files.pythonhosted.org/packages/f6/40/10fa93811fd439341fad7e0718a86aca0de9548023bbb403668d6555acab/yarl-1.23.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:b5405bb8f0e783a988172993cfc627e4d9d00432d6bbac65a923041edacf997d", size = 93960, upload-time = "2026-03-01T22:05:28.738Z" }, - { url = "https://files.pythonhosted.org/packages/bc/d2/8ae2e6cd77d0805f4526e30ec43b6f9a3dfc542d401ac4990d178e4bf0cf/yarl-1.23.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:1c3a3598a832590c5a3ce56ab5576361b5688c12cb1d39429cf5dba30b510760", size = 104703, upload-time = "2026-03-01T22:05:30.438Z" }, - { url = "https://files.pythonhosted.org/packages/2f/0c/b3ceacf82c3fe21183ce35fa2acf5320af003d52bc1fcf5915077681142e/yarl-1.23.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:8419ebd326430d1cbb7efb5292330a2cf39114e82df5cc3d83c9a0d5ebeaf2f2", size = 98325, upload-time = "2026-03-01T22:05:31.835Z" }, - { url = "https://files.pythonhosted.org/packages/9d/e0/12900edd28bdab91a69bd2554b85ad7b151f64e8b521fe16f9ad2f56477a/yarl-1.23.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:be61f6fff406ca40e3b1d84716fde398fc08bc63dd96d15f3a14230a0973ed86", size = 105067, upload-time = "2026-03-01T22:05:33.358Z" }, - { url = "https://files.pythonhosted.org/packages/15/61/74bb1182cf79c9bbe4eb6b1f14a57a22d7a0be5e9cedf8e2d5c2086474c3/yarl-1.23.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3ceb13c5c858d01321b5d9bb65e4cf37a92169ea470b70fec6f236b2c9dd7e34", size = 100285, upload-time = "2026-03-01T22:05:35.4Z" }, - { url = "https://files.pythonhosted.org/packages/69/7f/cd5ef733f2550de6241bd8bd8c3febc78158b9d75f197d9c7baa113436af/yarl-1.23.0-cp312-cp312-win32.whl", hash = "sha256:fffc45637bcd6538de8b85f51e3df3223e4ad89bccbfca0481c08c7fc8b7ed7d", size = 82359, upload-time = "2026-03-01T22:05:36.811Z" }, - { url = "https://files.pythonhosted.org/packages/f5/be/25216a49daeeb7af2bec0db22d5e7df08ed1d7c9f65d78b14f3b74fd72fc/yarl-1.23.0-cp312-cp312-win_amd64.whl", hash = "sha256:f69f57305656a4852f2a7203efc661d8c042e6cc67f7acd97d8667fb448a426e", size = 87674, upload-time = "2026-03-01T22:05:38.171Z" }, - { url = "https://files.pythonhosted.org/packages/d2/35/aeab955d6c425b227d5b7247eafb24f2653fedc32f95373a001af5dfeb9e/yarl-1.23.0-cp312-cp312-win_arm64.whl", hash = "sha256:6e87a6e8735b44816e7db0b2fbc9686932df473c826b0d9743148432e10bb9b9", size = 81879, upload-time = "2026-03-01T22:05:40.006Z" }, - { url = "https://files.pythonhosted.org/packages/69/68/c8739671f5699c7dc470580a4f821ef37c32c4cb0b047ce223a7f115757f/yarl-1.23.0-py3-none-any.whl", hash = "sha256:a2df6afe50dea8ae15fa34c9f824a3ee958d785fd5d089063d960bae1daa0a3f", size = 48288, upload-time = "2026-03-01T22:07:51.388Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/79/12/1e8f37460ea0f7eb59c221fdaf0ed75e7ac43e97f8093b9c6f411df50a78/yarl-1.24.2.tar.gz", hash = "sha256:9ac374123c6fd7abf64d1fec93962b0bd4ee2c19751755a762a72dd96c0378f8", size = 210798, upload-time = "2026-05-19T21:31:05.599Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f0/da/866bcb01076ba49d2b42b309867bed3826421f1c479655eb7a607b44f20b/yarl-1.24.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:b975866c184564c827e0877380f0dae57dcca7e52782128381b72feff6dfceb8", size = 129957, upload-time = "2026-05-19T21:28:51.695Z" }, + { url = "https://files.pythonhosted.org/packages/bf/1d/fcefb70922ea2268a8971d8e5874d9a8218644200fb8465f1dcad55e6851/yarl-1.24.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:3b075301a2836a0e297b1b658cb6d6135df535d62efefdd60366bd589c2c82f2", size = 92164, upload-time = "2026-05-19T21:28:53.242Z" }, + { url = "https://files.pythonhosted.org/packages/29/b6/170e2b8d4e3bc30e6bfdcca53556537f5bf595e938632dfcb059311f3ff6/yarl-1.24.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8ae44649b00947634ab0dab2a374a638f52923a6e67083f2c156cd5cbd1a881d", size = 91688, upload-time = "2026-05-19T21:28:54.865Z" }, + { url = "https://files.pythonhosted.org/packages/fe/a5/c9f655d5553ea0b99fdac9d6a99ad3f9b3e73b8e5758bb46f58c9831f74c/yarl-1.24.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:507cc19f0b45454e2d6dcd62ff7d062b9f77a2812404e62dbdaec05b50faa035", size = 102902, upload-time = "2026-05-19T21:28:56.963Z" }, + { url = "https://files.pythonhosted.org/packages/5d/bc/6b9664d815d79af4ee553337f9d606c56bbf269186ada9172de45f1b5f60/yarl-1.24.2-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:c4c17bad5a530912d2111825d3f05e89bab2dd376aaa8cbc77e449e6db63e576", size = 97931, upload-time = "2026-05-19T21:28:58.56Z" }, + { url = "https://files.pythonhosted.org/packages/98/ec/32ba48acae30fecd60928f5791188b80a9d6ee3840507ffda29fecd37b71/yarl-1.24.2-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f5f0cbb112838a4a293985b6ed73948a547dadcc1ba6d2089938e7abdedceef8", size = 111030, upload-time = "2026-05-19T21:29:00.148Z" }, + { url = "https://files.pythonhosted.org/packages/82/5a/6f4cd081e5f4934d2ae3a8ef4abe3afacc010d26f0035ee91b35cd7d7c37/yarl-1.24.2-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5ec8356b8a6afcf81fc7aeeef13b1ff7a49dec00f313394bbb9e83830d32ccd7", size = 110392, upload-time = "2026-05-19T21:29:02.155Z" }, + { url = "https://files.pythonhosted.org/packages/7a/da/323a01c349bd5fb01bb6652e314d9bb218cee630a736bdb810ad50e4013f/yarl-1.24.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7e7ebcdef69dec6c6451e616f32b622a6d4a2e92b445c992f7c8e5274a6bbc4c", size = 105612, upload-time = "2026-05-19T21:29:04.247Z" }, + { url = "https://files.pythonhosted.org/packages/7c/80/264ab684f181e1a876389374519ff05d10248725535ae2ac4e8ac4e563d6/yarl-1.24.2-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:47a55d6cf6db2f401017a9e96e5288844e5051911fb4e0c8311a3980f5e59a7d", size = 104487, upload-time = "2026-05-19T21:29:06.491Z" }, + { url = "https://files.pythonhosted.org/packages/41/07/efabe5df87e96d7ad5959760b888344be48cd6884db127b407c6b5503adc/yarl-1.24.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3065657c80a2321225e804048597ad55658a7e76b32d6f5ee4074d04c50401db", size = 102333, upload-time = "2026-05-19T21:29:08.267Z" }, + { url = "https://files.pythonhosted.org/packages/44/0c/bcf7c42603e1009295f586d8890f2ba032c8b53310e815adf0a202c73d9f/yarl-1.24.2-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:cb84b80d88e19ede158619b80813968713d8d008b0e2497a576e6a0557d50712", size = 99025, upload-time = "2026-05-19T21:29:10.682Z" }, + { url = "https://files.pythonhosted.org/packages/4f/82/84482ab1a57a0f21a08afe6a7004c61d741f8f2ecc3b05c321577c612164/yarl-1.24.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:990de4f680b1c217e77ff0d6aa0029f9eb79889c11fb3e9a3942c7eba29c1996", size = 110507, upload-time = "2026-05-19T21:29:12.954Z" }, + { url = "https://files.pythonhosted.org/packages/c4/8d/a546ba1dfe1b0f290e05fef145cd07614c0f15df1a707195e512d1e39d1d/yarl-1.24.2-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:abb8ec0323b80161e3802da3150ef660b41d0e9be2048b76a363d93eee992c2b", size = 103719, upload-time = "2026-05-19T21:29:14.893Z" }, + { url = "https://files.pythonhosted.org/packages/1a/b6/267f2a09213138473adfce6b8a6e17791d7fee70bd4d9003218e4dec58b0/yarl-1.24.2-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:e7977781f83638a4c73e0f88425563d70173e0dfd90ac006a45c65036293ee3c", size = 110438, upload-time = "2026-05-19T21:29:16.485Z" }, + { url = "https://files.pythonhosted.org/packages/48/2d/1c8d89c7c5f9cad9fb2902445d94e2ab1d7aa35de029afbb8ae95c42d00f/yarl-1.24.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e30dd55825dc554ec5b66a94953b8eda8745926514c5089dfcacecb9c99b5bd1", size = 105719, upload-time = "2026-05-19T21:29:18.367Z" }, + { url = "https://files.pythonhosted.org/packages/a7/25/722e3b93bd687009afb2d59a35e13d30ddd8f80571445bb0c4e4ce26ec66/yarl-1.24.2-cp312-cp312-win_amd64.whl", hash = "sha256:7dafe10c12ddd4d120d528c4b5599c953bd7b12845347d507b95451195bb6cad", size = 92901, upload-time = "2026-05-19T21:29:20.014Z" }, + { url = "https://files.pythonhosted.org/packages/39/47/4486ccfb674c04854a1ef8aa77868b6a6f765feaf69633409d7ca4f02cb8/yarl-1.24.2-cp312-cp312-win_arm64.whl", hash = "sha256:044a09d8401fcf8681977faef6d286b8ade1e2d2e9dceda175d1cfa5ca496f30", size = 87229, upload-time = "2026-05-19T21:29:22.1Z" }, + { url = "https://files.pythonhosted.org/packages/fd/4d/4b880086bd0d3e034d25647be1d830afc3e3f610e98c4ab3490af6b1b6d5/yarl-1.24.2-py3-none-any.whl", hash = "sha256:2783d9226db8797636cd6896e4de81feed252d1db72265686c9558d97a4d94b9", size = 53576, upload-time = "2026-05-19T21:31:03.909Z" }, ]