diff --git a/README.md b/README.md index 555d94f4..9f8ed1ec 100644 --- a/README.md +++ b/README.md @@ -65,8 +65,9 @@ See the [profile registry](docs/profiles/README.md) for recipe identities and ev ### GLM-5.3 Flash research observation **Research-only — 16K context, single observation.** The SparkCache-enabled -profile recorded 2,371 tok/s prefill and 36.06 tok/s sustained -C1 decode on random tokens. No A/B baseline has been completed. +profile recorded 2,371 tok/s prefill and 36.06 tok/s sustained C1 decode on +random tokens. No A/B baseline has been completed. C4 and C8 were capacity-limited +and are omitted rather than reported as throughput results. | Profile | Prefill | C1 decode | C8 decode | Highest valid decode | Coding peak | |---|---:|---:|---:|---:|---:| diff --git a/docs/GLM53_B12X_KDA_ADAPTIVE_MTP_SPARKCACHE_TP4_QUICKSTART.md b/docs/GLM53_B12X_KDA_ADAPTIVE_MTP_SPARKCACHE_TP4_QUICKSTART.md index f94b1065..a646bb7b 100644 --- a/docs/GLM53_B12X_KDA_ADAPTIVE_MTP_SPARKCACHE_TP4_QUICKSTART.md +++ b/docs/GLM53_B12X_KDA_ADAPTIVE_MTP_SPARKCACHE_TP4_QUICKSTART.md @@ -11,7 +11,7 @@ three, and a 32-step acceptance window. Fastsafetensors uses queue size one. TP4 makes the pinned vLLM loader select `nogds=True`, so model loading uses pipelined host I/O without GPU Direct Storage. -The profile reserves 20 GiB of FP8 KV per rank and enables SparkCache native +The profile reserves 20 GiB of FP8 KV per rank and enables SparkCache CUDA restore, tail-only publication, shared restore trunks, and bounded shared GPU prefix leases. Image construction and distribution do not require stopping an existing service. Do not run the launch command until all four ranks have the @@ -49,10 +49,10 @@ Record immutable local identities: ```bash sparkcache_image='sparkring-glm53-sparkcache:b12x-kda-adaptive-mtp-0b67266a-arm64' sparkcache_image_id="$(docker image inspect --format '{{.Id}}' "${sparkcache_image}")" -native_sha256="$(docker run --rm --entrypoint sha256sum "${sparkcache_image}" \ +cuda_placement_sha256="$(docker run --rm --entrypoint sha256sum "${sparkcache_image}" \ /opt/sparkcache-src/sparkcache/native/build-cuda/libspark_cache_placement.so \ | cut -d ' ' -f1)" -test "${#native_sha256}" -eq 64 +test "${#cuda_placement_sha256}" -eq 64 ``` The runtime builder verifies the complete first-parent vLLM history from @@ -72,7 +72,7 @@ python sparkring/scripts/prepare_glm53_b12x_kda_adaptive_mtp_profile.py \ --image-id "${sparkcache_image_id}" \ --parent-image "${runtime_image}" \ --parent-image-id "${runtime_image_id}" \ - --native-library-sha256 "${native_sha256}" \ + --cuda-placement-library-sha256 "${cuda_placement_sha256}" \ --profile-output profile.json \ --site-output site.yaml ``` diff --git a/docs/GLM53_E10536A_SPARKCACHE_TP4_QUICKSTART.md b/docs/GLM53_E10536A_SPARKCACHE_TP4_QUICKSTART.md index 6c0cda3c..829fbed2 100644 --- a/docs/GLM53_E10536A_SPARKCACHE_TP4_QUICKSTART.md +++ b/docs/GLM53_E10536A_SPARKCACHE_TP4_QUICKSTART.md @@ -19,8 +19,8 @@ pipelined shard loading without GPU Direct Storage. The queue can retain one additional shard-sized device buffer during model loading. All profiles use TP4/DCP1, 20 GiB FP8 KV per rank, a 524,288-token request -limit, 32 sequences, native direct restore, two restore lanes, eight native -I/O workers, two 256 MiB arenas, shared GPU-prefix leases, and one-shot cache +limit, 32 sequences, SparkCache CUDA restore, two restore lanes, eight CUDA +restore I/O workers, two 256 MiB placement arenas, shared GPU-prefix leases, and one-shot cache clearing. ## Build immutable inputs @@ -57,10 +57,10 @@ Record the exact output identities: ```bash sparkcache_image='sparkring-glm53-sparkcache:e10536a-source-arm64' sparkcache_image_id="$(docker image inspect --format '{{.Id}}' "${sparkcache_image}")" -native_sha256="$(docker run --rm --entrypoint sha256sum "${sparkcache_image}" \ +cuda_placement_sha256="$(docker run --rm --entrypoint sha256sum "${sparkcache_image}" \ /opt/sparkcache-src/sparkcache/native/build-cuda/libspark_cache_placement.so \ | cut -d ' ' -f1)" -test "${#native_sha256}" -eq 64 +test "${#cuda_placement_sha256}" -eq 64 ``` ## Resolve a serving profile @@ -88,7 +88,7 @@ python sparkring/scripts/prepare_glm53_e105_profile.py \ --image-id "${sparkcache_image_id}" \ --parent-image "${runtime_image}" \ --parent-image-id "${runtime_image_id}" \ - --native-library-sha256 "${native_sha256}" \ + --cuda-placement-library-sha256 "${cuda_placement_sha256}" \ --profile-output profile.json \ --site-output site.yaml ``` diff --git a/recipes/sparkcache/README.md b/recipes/sparkcache/README.md index e5d8c2ab..b2f9690f 100644 --- a/recipes/sparkcache/README.md +++ b/recipes/sparkcache/README.md @@ -90,7 +90,7 @@ Limitations: The published receipts use a 4,096-token scheduler budget; other budgets are operator choices whose performance and capacity behavior is not recorded here, except that the GLM-5.3 Flash receipt uses 8,192. The DeepSeek receipts cover DCP1, not DCP2 or DCP4. These -recipes disable streaming snapshots and native restore, so the receipts do not +recipes disable streaming snapshots and SparkCache CUDA restore, so the receipts do not cover either mode. Other images, checkpoints, and cache geometries are also outside the recorded evidence. The GLM-5.3 Flash receipt covers an 8,192-token restored span and does not establish throughput neutrality or larger-span diff --git a/recipes/sparkcache/deepseek-v4-flash-0731-tp2-dcp1.json b/recipes/sparkcache/deepseek-v4-flash-0731-tp2-dcp1.json index 2498ac72..247fa51c 100644 --- a/recipes/sparkcache/deepseek-v4-flash-0731-tp2-dcp1.json +++ b/recipes/sparkcache/deepseek-v4-flash-0731-tp2-dcp1.json @@ -64,7 +64,7 @@ "min_span_tokens": 256, "max_span_tokens": 1048576, "streaming_snapshots": false, - "native_restore": false, + "cuda_restore": false, "root_requirement": "Use a dedicated rank-local host directory mounted at the same container path on both ranks." }, "evidence": { @@ -78,7 +78,7 @@ "The 17179869184-byte reservation, block size 256, and 4096-token scheduler budget carry forward unchanged, but their capacity with the larger context and sequence target must be measured.", "The complete checkpoint manifest is generated from the pinned revision and must reproduce the recorded digest. A different digest is a different composition and does not reproduce this qualification.", "The published receipt covers DeepSeek DCP1, not DCP2 or DCP4.", - "This recipe disables streaming snapshots and native restore; the published receipt does not cover either mode." + "This recipe disables streaming snapshots and SparkCache CUDA restore; the published receipt does not cover either mode." ], "record": "recipes/sparkcache/README.md", "artifact_scope": "The exact SparkCache wheel, runtime image, checkpoint identity, topology, and historical max_model_len=131072, max_num_seqs=6 serving values stated in evidence.conditions.", diff --git a/recipes/sparkcache/deepseek-v4-flash-0731-tp4-dcp1.json b/recipes/sparkcache/deepseek-v4-flash-0731-tp4-dcp1.json index 0f0c1396..bde56c60 100644 --- a/recipes/sparkcache/deepseek-v4-flash-0731-tp4-dcp1.json +++ b/recipes/sparkcache/deepseek-v4-flash-0731-tp4-dcp1.json @@ -54,7 +54,7 @@ "min_span_tokens": 256, "max_span_tokens": 1048576, "streaming_snapshots": false, - "native_restore": false, + "cuda_restore": false, "root_requirement": "Use one dedicated rank-local host directory per physical rank, mounted at the same container path on all four ranks." }, "evidence": { @@ -68,7 +68,7 @@ "The 34359738368-byte reservation, 32-sequence limit, block size 256, and 4096-token scheduler budget carry forward unchanged, but capacity at the larger context must be measured.", "The complete checkpoint manifest is generated from the pinned revision and must reproduce the recorded digest. A different digest is a different composition and does not reproduce this qualification.", "The published receipt covers DeepSeek DCP1, not DCP2 or DCP4.", - "This recipe disables streaming snapshots and native restore; the published receipt does not cover either mode." + "This recipe disables streaming snapshots and SparkCache CUDA restore; the published receipt does not cover either mode." ], "record": "recipes/sparkcache/README.md", "artifact_scope": "The exact SparkCache wheel, runtime image, checkpoint identity, topology, and historical max_model_len=524288 serving values stated in evidence.conditions.", diff --git a/recipes/sparkcache/glm52-exl3-r7-3.5bpw-tp4-dcp4.json b/recipes/sparkcache/glm52-exl3-r7-3.5bpw-tp4-dcp4.json index f4ad30c8..10050b5c 100644 --- a/recipes/sparkcache/glm52-exl3-r7-3.5bpw-tp4-dcp4.json +++ b/recipes/sparkcache/glm52-exl3-r7-3.5bpw-tp4-dcp4.json @@ -57,7 +57,7 @@ "min_span_tokens": 256, "max_span_tokens": 1048576, "streaming_snapshots": false, - "native_restore": false, + "cuda_restore": false, "root_requirement": "Use one dedicated rank-local host directory per physical rank, mounted at the same container path on all four ranks." }, "restart_contract": { @@ -75,7 +75,7 @@ "The qualified receipt covers max_model_len=262144; the current max_model_len=1048576 target requires a larger CKV-gather workspace and live memory validation.", "The qualified operator image is identified by local image ID but has no published immutable image reference. A clean-checkout rebuild has implemented status until it passes the promotion checklist.", "The qualified lane requires preserving and regenerating the 40-query-row exact-state receipt, whose path contains q40-exact-state-serving-v1, before every coordinated restart.", - "This recipe disables streaming snapshots and native restore; the published receipt does not cover either mode.", + "This recipe disables streaming snapshots and SparkCache CUDA restore; the published receipt does not cover either mode.", "Full GLM reasoning-trace equality is an inconclusive diagnostic under this runtime: repeated fixed-seed requests produced different non-empty reasoning bodies with identical final content and finish reason stop." ], "record": "https://github.com/FujitsuPolycom/sparkcache/blob/999bc13d6c0b52a6cee3c90487ac9b8643ba3f99/GLM52_A2_LIVE_VALIDATION.md", diff --git a/recipes/sparkcache/glm53-flash-nvfp4-dflash2-bf16-tp4-dcp1.json b/recipes/sparkcache/glm53-flash-nvfp4-dflash2-bf16-tp4-dcp1.json index 7b0a3d75..cb43e1fe 100644 --- a/recipes/sparkcache/glm53-flash-nvfp4-dflash2-bf16-tp4-dcp1.json +++ b/recipes/sparkcache/glm53-flash-nvfp4-dflash2-bf16-tp4-dcp1.json @@ -83,7 +83,7 @@ "min_span_tokens": 4096, "max_span_tokens": 524288, "streaming_snapshots": false, - "native_restore": false, + "cuda_restore": false, "root_requirement": "Use one dedicated rank-local host directory per physical rank, mounted at /cache/jit on every rank." }, "evidence": { @@ -97,7 +97,7 @@ "A rebuilt image has implemented status until its immutable digest passes the same live qualification.", "The first request after API startup recomputed because the scheduler had not received a complete four-rank inventory checkpoint; the following identical request formed quorum and restored.", "The receipt does not establish throughput neutrality or restore performance for spans larger than 8192 tokens.", - "Streaming snapshots, native direct restore, MTP drafting, and other DFlash checkpoints are unsupported by this evidence.", + "Streaming snapshots, SparkCache CUDA restore, MTP drafting, and other DFlash checkpoints are unsupported by this evidence.", "The source-built runtime uses the Triton KDA prefill backend and stock safetensors loading; FlashKDA prefill and InstantTensor checkpoint loading are unsupported by this image.", "The optional deep_ep import emits a duplicate-NCCL warning. vLLM selects the source-built NCCL library through VLLM_NCCL_SO_PATH and serving proceeds.", "The target repository does not record its base-checkpoint revision." diff --git a/runtime/glm53-flash-adaptive-mtp-python-overlay/Containerfile b/runtime/glm53-flash-adaptive-mtp-python-overlay/Containerfile index cd24653f..8284e712 100644 --- a/runtime/glm53-flash-adaptive-mtp-python-overlay/Containerfile +++ b/runtime/glm53-flash-adaptive-mtp-python-overlay/Containerfile @@ -13,18 +13,18 @@ RUN test "$(git -C /build/b12x rev-parse HEAD)" = "${B12X_COMMIT}" \ --no-deps --wheel-dir /out/wheels /build/b12x \ && sha256sum /out/wheels/b12x-*.whl > /out/wheels/b12x-wheel.sha256 -FROM ${ARM_BUILDER} AS sparkcache-native -COPY bundle/sources/sparkcache/sparkcache/native /build/sparkcache-native -RUN cmake -S /build/sparkcache-native \ - -B /build/sparkcache-native/build-cuda \ +FROM ${ARM_BUILDER} AS sparkcache-cuda-placement +COPY bundle/sources/sparkcache/sparkcache/native /build/sparkcache-cuda-placement +RUN cmake -S /build/sparkcache-cuda-placement \ + -B /build/sparkcache-cuda-placement/build-cuda \ -DCMAKE_BUILD_TYPE=Release \ -DCMAKE_CUDA_ARCHITECTURES=121 \ -DSPARK_CACHE_PLACEMENT_ENABLE_CUDA=ON \ - && cmake --build /build/sparkcache-native/build-cuda \ + && cmake --build /build/sparkcache-cuda-placement/build-cuda \ --target spark_cache_placement -j"$(nproc)" \ - && test -s /build/sparkcache-native/build-cuda/libspark_cache_placement.so \ - && sha256sum /build/sparkcache-native/build-cuda/libspark_cache_placement.so \ - > /build/sparkcache-native/build-cuda/libspark_cache_placement.so.sha256 + && test -s /build/sparkcache-cuda-placement/build-cuda/libspark_cache_placement.so \ + && sha256sum /build/sparkcache-cuda-placement/build-cuda/libspark_cache_placement.so \ + > /build/sparkcache-cuda-placement/build-cuda/libspark_cache_placement.so.sha256 FROM ${PUBLIC_BASE} @@ -43,7 +43,7 @@ ARG SOURCE_RECEIPT_SHA256 ARG OVERLAY_MANIFEST_SHA256 ARG NATIVE_ELF_MANIFEST_SHA256 ARG NATIVE_DISPATCH_MANIFEST_SHA256 -ARG SPARKCACHE_NATIVE_SHA256 +ARG SPARKCACHE_CUDA_PLACEMENT_SHA256 ENV PYTHON_OVERLAY_ROOT=/opt/sparkring/runtime/python-overlay \ VLLM_CACHE_ROOT=/cache/jit/vllm/py-0b67266-native-da4d7be \ @@ -98,11 +98,11 @@ RUN actual_source="$(python3 -c 'import importlib.util, pathlib; spec = importli && test "${actual_source}" = "${SPARKCACHE_SOURCE_SHA256}" \ && printf '%s\n' "${actual_source}" \ > "${PYTHON_OVERLAY_ROOT}/sparkcache-source-tree.sha256" -COPY --from=sparkcache-native \ - /build/sparkcache-native/build-cuda/libspark_cache_placement.so \ +COPY --from=sparkcache-cuda-placement \ + /build/sparkcache-cuda-placement/build-cuda/libspark_cache_placement.so \ /opt/sparkcache-src/sparkcache/native/build-cuda/libspark_cache_placement.so -COPY --from=sparkcache-native \ - /build/sparkcache-native/build-cuda/libspark_cache_placement.so.sha256 \ +COPY --from=sparkcache-cuda-placement \ + /build/sparkcache-cuda-placement/build-cuda/libspark_cache_placement.so.sha256 \ /opt/sparkcache-src/sparkcache/native/build-cuda/libspark_cache_placement.so.sha256 COPY bundle/sources/sparkcache/patches/vllm-glm53-b12x-kda-adaptive-mtp \ /opt/sparkcache-vllm-patches @@ -112,9 +112,9 @@ COPY bundle/sources/b12x/LICENSE /usr/share/licenses/B12X/LICENSE COPY bundle/sources/sparkcache/LICENSE /usr/share/licenses/SparkCache/LICENSE RUN set -eu; \ - actual_native="$(sha256sum /opt/sparkcache-src/sparkcache/native/build-cuda/libspark_cache_placement.so | cut -d' ' -f1)"; \ - printf 'sparkcache_native_sha256=%s\n' "${actual_native}"; \ - test "${actual_native}" = "${SPARKCACHE_NATIVE_SHA256}"; \ + actual_cuda_placement="$(sha256sum /opt/sparkcache-src/sparkcache/native/build-cuda/libspark_cache_placement.so | cut -d' ' -f1)"; \ + printf 'sparkcache_cuda_placement_sha256=%s\n' "${actual_cuda_placement}"; \ + test "${actual_cuda_placement}" = "${SPARKCACHE_CUDA_PLACEMENT_SHA256}"; \ root=/usr/local/lib/python3.12/dist-packages; \ config="${root}/vllm/config/vllm.py"; \ config_sha256="$(sha256sum "${config}" | cut -d' ' -f1)"; \ @@ -171,7 +171,7 @@ LABEL org.opencontainers.image.title="SparkRing GLM-5.3 adaptive-MTP Python over org.sparkcache.source-revision="${SPARKCACHE_COMMIT}" \ org.sparkcache.source-tree="${SPARKCACHE_TREE}" \ org.sparkcache.source-sha256="${SPARKCACHE_SOURCE_SHA256}" \ - org.sparkcache.native-library-sha256="${SPARKCACHE_NATIVE_SHA256}" \ + org.sparkcache.cuda-placement-library-sha256="${SPARKCACHE_CUDA_PLACEMENT_SHA256}" \ org.sparkcache.vllm-contract-sha256="6defde9551cbb586fd09bb2d3020495531b6573397875a767eaae1dbad126024" \ org.sparkcache.deployment-profile="glm53-flash-adaptive-mtp-python-overlay" \ org.sparkring.source-receipt-sha256="${SOURCE_RECEIPT_SHA256}" diff --git a/runtime/glm53-flash-adaptive-mtp-python-overlay/README.md b/runtime/glm53-flash-adaptive-mtp-python-overlay/README.md index 54d22fba..0c9bfbc6 100644 --- a/runtime/glm53-flash-adaptive-mtp-python-overlay/README.md +++ b/runtime/glm53-flash-adaptive-mtp-python-overlay/README.md @@ -31,7 +31,7 @@ placement library is commit `e864ed9ad64f771188fdb59aa9738e348134d636`. The builder verifies clean deployable-source SHA-256 `f7c0565521fddeff7085e4cc08043cb8d1e2bde33abc67f83b8608a162d05b88` -before generating the native library. It applies the VMM exemption, +before generating the SparkCache CUDA placement library. It applies the VMM exemption, load-failure recovery, shared-prefix retention, and follower-attachment patches in order, then runs the eleven-file lease-contract verifier. The test profile selects `spark_cache_publication_schema=tail-cow-v1`, which maps opaque @@ -50,7 +50,7 @@ bash runtime/glm53-flash-adaptive-mtp-python-overlay/build-image.sh The script pulls and verifies the immutable public base, fetches exact source commits into a temporary build context, builds a pure B12X wheel and the -SparkCache placement library, creates the composed image, and writes a local +SparkCache CUDA placement library, creates the composed image, and writes a local receipt. It does not push an image or contact serving hosts. ## Resolve and inspect the four-rank plan @@ -68,7 +68,7 @@ python scripts/prepare_glm53_public_python_overlay_profile.py \ --site-template /path/to/resolved-glm53-site.yaml \ --image "$image" \ --image-id "$(jq -r .image_id "$receipt")" \ - --native-library-sha256 "$(jq -r .artifacts.sparkcache_native_sha256 "$receipt")" \ + --cuda-placement-library-sha256 "$(jq -r .artifacts.sparkcache_cuda_placement_sha256 "$receipt")" \ --native-elf-manifest-sha256 "$(jq -r .runtime_contract.native_elf_manifest_sha256 "$receipt")" \ --native-dispatch-manifest-sha256 "$(jq -r .runtime_contract.native_dispatch_manifest_sha256 "$receipt")" \ --source-receipt-sha256 "$(jq -r .artifacts.source_receipt_sha256 "$receipt")" \ diff --git a/runtime/glm53-flash-adaptive-mtp-python-overlay/build-image.sh b/runtime/glm53-flash-adaptive-mtp-python-overlay/build-image.sh index 99a9b41f..c78118b3 100644 --- a/runtime/glm53-flash-adaptive-mtp-python-overlay/build-image.sh +++ b/runtime/glm53-flash-adaptive-mtp-python-overlay/build-image.sh @@ -80,17 +80,17 @@ mkdir -p "${context}/base-probe" --output /out/retained-native.json >/dev/null native_elf_manifest_sha256="$(python3 -c 'import json,sys; print(json.load(open(sys.argv[1], encoding="utf-8"))["native_elf_manifest_sha256"])' "${context}/base-probe/retained-native.json")" native_dispatch_manifest_sha256="$(python3 -c 'import json,sys; print(json.load(open(sys.argv[1], encoding="utf-8"))["native_dispatch_manifest_sha256"])' "${context}/base-probe/retained-native.json")" -native_stage="sparkring-sparkcache-native:${sparkcache_commit:0:12}-${sparkring_revision:0:12}" +cuda_placement_stage="sparkring-sparkcache-cuda-placement:${sparkcache_commit:0:12}-${sparkring_revision:0:12}" "${engine}" build \ --platform linux/arm64 \ - --target sparkcache-native \ + --target sparkcache-cuda-placement \ --file "${context}/bundle/runtime/Containerfile" \ --build-arg "ARM_BUILDER=${arm_builder}" \ - --tag "${native_stage}" \ + --tag "${cuda_placement_stage}" \ "${context}" -sparkcache_native_sha256="$("${engine}" run --rm --entrypoint sha256sum \ - "${native_stage}" \ - /build/sparkcache-native/build-cuda/libspark_cache_placement.so | cut -d' ' -f1)" +sparkcache_cuda_placement_sha256="$("${engine}" run --rm --entrypoint sha256sum \ + "${cuda_placement_stage}" \ + /build/sparkcache-cuda-placement/build-cuda/libspark_cache_placement.so | cut -d' ' -f1)" "${engine}" build \ --platform linux/arm64 \ @@ -111,7 +111,7 @@ sparkcache_native_sha256="$("${engine}" run --rm --entrypoint sha256sum \ --build-arg "OVERLAY_MANIFEST_SHA256=${overlay_manifest_sha256}" \ --build-arg "NATIVE_ELF_MANIFEST_SHA256=${native_elf_manifest_sha256}" \ --build-arg "NATIVE_DISPATCH_MANIFEST_SHA256=${native_dispatch_manifest_sha256}" \ - --build-arg "SPARKCACHE_NATIVE_SHA256=${sparkcache_native_sha256}" \ + --build-arg "SPARKCACHE_CUDA_PLACEMENT_SHA256=${sparkcache_cuda_placement_sha256}" \ --tag "${image}" \ "${context}" diff --git a/runtime/glm53-flash-adaptive-mtp-python-overlay/test_public_python_overlay.py b/runtime/glm53-flash-adaptive-mtp-python-overlay/test_public_python_overlay.py index 907138c0..b42fdd40 100644 --- a/runtime/glm53-flash-adaptive-mtp-python-overlay/test_public_python_overlay.py +++ b/runtime/glm53-flash-adaptive-mtp-python-overlay/test_public_python_overlay.py @@ -154,8 +154,10 @@ def test_containerfile_reuses_vllm_and_nccl_native_artifacts() -> None: ) source_verification = recipe.index('printf \'sparkcache_source_sha256=%s') source_receipt = recipe.index("sparkcache-source-tree.sha256") - native_copy = recipe.index("COPY --from=sparkcache-native", source_verification) - assert source_copy < source_verification < source_receipt < native_copy + cuda_placement_copy = recipe.index( + "COPY --from=sparkcache-cuda-placement", source_verification + ) + assert source_copy < source_verification < source_receipt < cuda_placement_copy def test_image_verifier_reads_the_clean_sparkcache_source_receipt() -> None: diff --git a/runtime/glm53-flash-adaptive-mtp-python-overlay/verify_image.py b/runtime/glm53-flash-adaptive-mtp-python-overlay/verify_image.py index b7e8b76a..942460a0 100644 --- a/runtime/glm53-flash-adaptive-mtp-python-overlay/verify_image.py +++ b/runtime/glm53-flash-adaptive-mtp-python-overlay/verify_image.py @@ -163,14 +163,14 @@ def artifact_probe(engine: str, image: str) -> dict[str, Any]: import pathlib root = pathlib.Path('/opt/sparkring/runtime/python-overlay') -native = pathlib.Path('/opt/sparkcache-src/sparkcache/native/build-cuda/libspark_cache_placement.so') +cuda_placement = pathlib.Path('/opt/sparkcache-src/sparkcache/native/build-cuda/libspark_cache_placement.so') contract = pathlib.Path('/opt/sparkcache-src/sparkcache/runtime_patches/vllm-kv-block-lease-contract-glm53-b12x-kda-adaptive-mtp.json') sparkcache_source_receipt = root / 'sparkcache-source-tree.sha256' wheel_receipt = (root / 'b12x-wheel.sha256').read_text(encoding='utf-8').split() print(json.dumps({ 'b12x_wheel_sha256': wheel_receipt[0], 'b12x_wheel': pathlib.Path(wheel_receipt[1]).name, - 'sparkcache_native_sha256': hashlib.sha256(native.read_bytes()).hexdigest(), + 'sparkcache_cuda_placement_sha256': hashlib.sha256(cuda_placement.read_bytes()).hexdigest(), 'sparkcache_contract_sha256': hashlib.sha256(contract.read_bytes()).hexdigest(), 'sparkcache_source_tree_sha256': sparkcache_source_receipt.read_text(encoding='utf-8').strip(), 'source_receipt_sha256': hashlib.sha256((root / 'source-receipt.json').read_bytes()).hexdigest(), @@ -210,7 +210,7 @@ def verify_image(engine: str, image: str, pins_path: Path = PINS) -> dict[str, A raise VerifyError("clean SparkCache source receipt differs from its pin") for name in ( "b12x_wheel_sha256", - "sparkcache_native_sha256", + "sparkcache_cuda_placement_sha256", "source_receipt_sha256", ): if SHA256.fullmatch(str(artifacts.get(name, ""))) is None: @@ -223,8 +223,8 @@ def verify_image(engine: str, image: str, pins_path: Path = PINS) -> dict[str, A "org.sparkring.vllm.native-dispatch-manifest-sha256": runtime[ "native_dispatch_manifest_sha256" ], - "org.sparkcache.native-library-sha256": artifacts[ - "sparkcache_native_sha256" + "org.sparkcache.cuda-placement-library-sha256": artifacts[ + "sparkcache_cuda_placement_sha256" ], "org.sparkring.source-receipt-sha256": artifacts[ "source_receipt_sha256" diff --git a/runtime/glm53-flash-b12x-kda-adaptive-mtp/README.md b/runtime/glm53-flash-b12x-kda-adaptive-mtp/README.md index 0235e100..f43ba2fb 100644 --- a/runtime/glm53-flash-b12x-kda-adaptive-mtp/README.md +++ b/runtime/glm53-flash-b12x-kda-adaptive-mtp/README.md @@ -32,7 +32,7 @@ receipt proves image construction only. Startup, semantic generation, SparkCache restore, shared-prefix concurrency, and fatal-log checks require a separate four-rank receipt. -The matching SparkCache reconstructed-page placement source is commit +The matching SparkCache CUDA placement source is commit `5d571018de5b63a9a90e5c11e6d6e86bbff4a957`, Git tree `e864ed9ad64f771188fdb59aa9738e348134d636`, with clean deployable-source SHA-256 `f7c0565521fddeff7085e4cc08043cb8d1e2bde33abc67f83b8608a162d05b88`. diff --git a/scripts/config/README.md b/scripts/config/README.md index 76291c21..a0d041de 100644 --- a/scripts/config/README.md +++ b/scripts/config/README.md @@ -86,7 +86,7 @@ uses these sanitized inputs: | File | Role | |---|---| | `glm53-flash-b12x-kda-adaptive-mtp-tp4-site.example.yaml` | Four-rank TP4/DCP1 site and 20 GiB FP8 KV reservation per rank | -| `glm53-flash-b12x-kda-mtp5-adaptive-fastsafetensors-sparkcache-tp4-dcp1.example.json` | Adaptive MTP 3→5, 32-step window, fastsafetensors queue one, native SparkCache restore | +| `glm53-flash-b12x-kda-mtp5-adaptive-fastsafetensors-sparkcache-tp4-dcp1.example.json` | Adaptive MTP 3→5, 32-step window, fastsafetensors queue one, SparkCache CUDA restore | Resolve both templates with `scripts/prepare_glm53_b12x_kda_adaptive_mtp_profile.py`. The resolver rejects diff --git a/scripts/config/glm53-flash-b12x-kda-mtp5-adaptive-fastsafetensors-sparkcache-tp4-dcp1.example.json b/scripts/config/glm53-flash-b12x-kda-mtp5-adaptive-fastsafetensors-sparkcache-tp4-dcp1.example.json index c1e64872..4a6d3adc 100644 --- a/scripts/config/glm53-flash-b12x-kda-mtp5-adaptive-fastsafetensors-sparkcache-tp4-dcp1.example.json +++ b/scripts/config/glm53-flash-b12x-kda-mtp5-adaptive-fastsafetensors-sparkcache-tp4-dcp1.example.json @@ -82,7 +82,7 @@ "--async-scheduling", "--enable-prefix-caching", "--cudagraph-metrics", - "--kv-transfer-config", "{\"kv_connector\":\"SparkContextCacheConnector\",\"kv_connector_module_path\":\"sparkcache.spark_context_cache_connector\",\"kv_role\":\"kv_both\",\"kv_load_failure_policy\":\"recompute\",\"kv_connector_extra_config\":{\"spark_cache_root\":\"/cache/jit/sparkcache-context\",\"spark_cache_model_profile\":\"glm53-flash-hybrid\",\"spark_cache_target_checkpoint_sha256\":\"a35e6bf2875c1875609b8deaec404c07c6cc80259e4222fc0b51e649498bd6b9\",\"spark_cache_draft_checkpoint_sha256\":\"3255539158b8a4fd199b4d97d89eb5231df3b39a5370881c16099a8059b09e44\",\"spark_cache_draft_policy\":\"separate\",\"spark_cache_store\":true,\"spark_cache_restore\":true,\"spark_cache_scheduler_probe\":\"none\",\"spark_cache_streaming_snapshots\":false,\"spark_cache_native_restore\":true,\"spark_cache_max_bytes\":51539607552,\"spark_cache_low_watermark_bytes\":42949672960,\"spark_cache_ttl_seconds\":0,\"spark_cache_min_span_tokens\":4096,\"spark_cache_max_span_tokens\":524288,\"spark_cache_native_library\":\"/opt/sparkcache-src/sparkcache/native/build-cuda/libspark_cache_placement.so\",\"spark_cache_native_library_sha256\":\"REPLACE_WITH_NATIVE_LIBRARY_SHA256\",\"spark_cache_native_arena_bytes\":268435456,\"spark_cache_native_io_workers\":8,\"spark_cache_load_threads\":2,\"spark_cache_clear_once\":\"sparkring-b12x-kda-adaptive-mtp-fastsafetensors-initialization\"}}" + "--kv-transfer-config", "{\"kv_connector\":\"SparkContextCacheConnector\",\"kv_connector_module_path\":\"sparkcache.spark_context_cache_connector\",\"kv_role\":\"kv_both\",\"kv_load_failure_policy\":\"recompute\",\"kv_connector_extra_config\":{\"spark_cache_root\":\"/cache/jit/sparkcache-context\",\"spark_cache_model_profile\":\"glm53-flash-hybrid\",\"spark_cache_target_checkpoint_sha256\":\"a35e6bf2875c1875609b8deaec404c07c6cc80259e4222fc0b51e649498bd6b9\",\"spark_cache_draft_checkpoint_sha256\":\"3255539158b8a4fd199b4d97d89eb5231df3b39a5370881c16099a8059b09e44\",\"spark_cache_draft_policy\":\"separate\",\"spark_cache_store\":true,\"spark_cache_restore\":true,\"spark_cache_scheduler_probe\":\"none\",\"spark_cache_streaming_snapshots\":false,\"spark_cache_cuda_restore\":true,\"spark_cache_max_bytes\":51539607552,\"spark_cache_low_watermark_bytes\":42949672960,\"spark_cache_ttl_seconds\":0,\"spark_cache_min_span_tokens\":4096,\"spark_cache_max_span_tokens\":524288,\"spark_cache_cuda_placement_library\":\"/opt/sparkcache-src/sparkcache/native/build-cuda/libspark_cache_placement.so\",\"spark_cache_cuda_placement_library_sha256\":\"REPLACE_WITH_CUDA_PLACEMENT_LIBRARY_SHA256\",\"spark_cache_cuda_placement_arena_bytes\":268435456,\"spark_cache_cuda_restore_io_workers\":8,\"spark_cache_load_threads\":2,\"spark_cache_clear_once\":\"sparkring-b12x-kda-adaptive-mtp-fastsafetensors-initialization\"}}" ], "extra_volumes": [ {"host": "/REPLACE/GLM53_CACHE_HOST_ROOT", "container": "/cache/jit", "mode": "rw"} @@ -134,6 +134,6 @@ "attestation_hook": [ "/bin/sh", "-ec", - "test -f /models/target/config.json && test -f /models/target/model.safetensors.index.json && test \"$(sha256sum /models/target/config.json | cut -d ' ' -f1)\" = 676382abd1e90a6c85f0c8f33d45441ecd45fd514fd7b63ce5610e732d8e4996 && test \"$(sha256sum /models/target/model.safetensors.index.json | cut -d ' ' -f1)\" = 0d1d9e6b226e76520e182de10d4e7194cc885c5cb1bf885bb90de1916ce312cb && test \"$(sha256sum /opt/sparkring/nccl/libnccl.so.2 | cut -d ' ' -f1)\" = 5f1c3f10d5ace66d4ba584415bbfe42b6ac1a0a9116a3b81dcbe50516ad924b3 && test \"$(python3 -c 'import importlib.util, pathlib; spec = importlib.util.spec_from_file_location(\"source_identity\", \"/opt/sparkcache-source-identity.py\"); module = importlib.util.module_from_spec(spec); spec.loader.exec_module(module); print(module.source_tree_sha256(pathlib.Path(\"/opt/sparkcache-src/sparkcache\")))')\" = f7c0565521fddeff7085e4cc08043cb8d1e2bde33abc67f83b8608a162d05b88 && test \"$(sha256sum /opt/sparkcache-src/sparkcache/runtime_patches/vllm-kv-block-lease-contract-glm53-b12x-kda-adaptive-mtp.json | cut -d ' ' -f1)\" = 6defde9551cbb586fd09bb2d3020495531b6573397875a767eaae1dbad126024 && test \"$(sha256sum /usr/local/lib/python3.12/dist-packages/vllm/config/vllm.py | cut -d ' ' -f1)\" = 9f64f5041f7f9d953e9f6bc53de8733b3eb4035c0753056a1f646346702a0994 && test \"$(sha256sum /opt/sparkcache-src/sparkcache/native/build-cuda/libspark_cache_placement.so | cut -d ' ' -f1)\" = REPLACE_WITH_NATIVE_LIBRARY_SHA256 && python3 /opt/sparkcache-src/sparkcache/runtime_patches/verify_lease_contract.py --vllm-root /usr/local/lib/python3.12/dist-packages --contract /opt/sparkcache-src/sparkcache/runtime_patches/vllm-kv-block-lease-contract-glm53-b12x-kda-adaptive-mtp.json" + "test -f /models/target/config.json && test -f /models/target/model.safetensors.index.json && test \"$(sha256sum /models/target/config.json | cut -d ' ' -f1)\" = 676382abd1e90a6c85f0c8f33d45441ecd45fd514fd7b63ce5610e732d8e4996 && test \"$(sha256sum /models/target/model.safetensors.index.json | cut -d ' ' -f1)\" = 0d1d9e6b226e76520e182de10d4e7194cc885c5cb1bf885bb90de1916ce312cb && test \"$(sha256sum /opt/sparkring/nccl/libnccl.so.2 | cut -d ' ' -f1)\" = 5f1c3f10d5ace66d4ba584415bbfe42b6ac1a0a9116a3b81dcbe50516ad924b3 && test \"$(python3 -c 'import importlib.util, pathlib; spec = importlib.util.spec_from_file_location(\"source_identity\", \"/opt/sparkcache-source-identity.py\"); module = importlib.util.module_from_spec(spec); spec.loader.exec_module(module); print(module.source_tree_sha256(pathlib.Path(\"/opt/sparkcache-src/sparkcache\")))')\" = f7c0565521fddeff7085e4cc08043cb8d1e2bde33abc67f83b8608a162d05b88 && test \"$(sha256sum /opt/sparkcache-src/sparkcache/runtime_patches/vllm-kv-block-lease-contract-glm53-b12x-kda-adaptive-mtp.json | cut -d ' ' -f1)\" = 6defde9551cbb586fd09bb2d3020495531b6573397875a767eaae1dbad126024 && test \"$(sha256sum /usr/local/lib/python3.12/dist-packages/vllm/config/vllm.py | cut -d ' ' -f1)\" = 9f64f5041f7f9d953e9f6bc53de8733b3eb4035c0753056a1f646346702a0994 && test \"$(sha256sum /opt/sparkcache-src/sparkcache/native/build-cuda/libspark_cache_placement.so | cut -d ' ' -f1)\" = REPLACE_WITH_CUDA_PLACEMENT_LIBRARY_SHA256 && python3 /opt/sparkcache-src/sparkcache/runtime_patches/verify_lease_contract.py --vllm-root /usr/local/lib/python3.12/dist-packages --contract /opt/sparkcache-src/sparkcache/runtime_patches/vllm-kv-block-lease-contract-glm53-b12x-kda-adaptive-mtp.json" ] } diff --git a/scripts/config/glm53-flash-dflash2-bf16-tp4-dcp1-sparkcache.example.json b/scripts/config/glm53-flash-dflash2-bf16-tp4-dcp1-sparkcache.example.json index 955191d3..e9272647 100644 --- a/scripts/config/glm53-flash-dflash2-bf16-tp4-dcp1-sparkcache.example.json +++ b/scripts/config/glm53-flash-dflash2-bf16-tp4-dcp1-sparkcache.example.json @@ -81,7 +81,7 @@ "--async-scheduling", "--enable-prefix-caching", "--cudagraph-metrics", - "--kv-transfer-config", "{\"kv_connector\":\"SparkContextCacheConnector\",\"kv_connector_module_path\":\"sparkcache.spark_context_cache_connector\",\"kv_role\":\"kv_both\",\"kv_load_failure_policy\":\"recompute\",\"kv_connector_extra_config\":{\"spark_cache_root\":\"/cache/jit/sparkcache-context\",\"spark_cache_model_profile\":\"glm53-flash-hybrid\",\"spark_cache_target_checkpoint_sha256\":\"a35e6bf2875c1875609b8deaec404c07c6cc80259e4222fc0b51e649498bd6b9\",\"spark_cache_draft_checkpoint_sha256\":\"b33c03475ba7322cf398828f2d8d1be376df30dc05c6b40c28c8ea8da23e410b\",\"spark_cache_draft_policy\":\"separate\",\"spark_cache_store\":true,\"spark_cache_restore\":true,\"spark_cache_scheduler_probe\":\"none\",\"spark_cache_streaming_snapshots\":false,\"spark_cache_native_restore\":false,\"spark_cache_max_bytes\":51539607552,\"spark_cache_low_watermark_bytes\":42949672960,\"spark_cache_ttl_seconds\":0,\"spark_cache_min_span_tokens\":4096,\"spark_cache_max_span_tokens\":524288}}" + "--kv-transfer-config", "{\"kv_connector\":\"SparkContextCacheConnector\",\"kv_connector_module_path\":\"sparkcache.spark_context_cache_connector\",\"kv_role\":\"kv_both\",\"kv_load_failure_policy\":\"recompute\",\"kv_connector_extra_config\":{\"spark_cache_root\":\"/cache/jit/sparkcache-context\",\"spark_cache_model_profile\":\"glm53-flash-hybrid\",\"spark_cache_target_checkpoint_sha256\":\"a35e6bf2875c1875609b8deaec404c07c6cc80259e4222fc0b51e649498bd6b9\",\"spark_cache_draft_checkpoint_sha256\":\"b33c03475ba7322cf398828f2d8d1be376df30dc05c6b40c28c8ea8da23e410b\",\"spark_cache_draft_policy\":\"separate\",\"spark_cache_store\":true,\"spark_cache_restore\":true,\"spark_cache_scheduler_probe\":\"none\",\"spark_cache_streaming_snapshots\":false,\"spark_cache_cuda_restore\":false,\"spark_cache_max_bytes\":51539607552,\"spark_cache_low_watermark_bytes\":42949672960,\"spark_cache_ttl_seconds\":0,\"spark_cache_min_span_tokens\":4096,\"spark_cache_max_span_tokens\":524288}}" ], "extra_volumes": [ {"host": "/REPLACE/DFLASH_MODEL_HOST_PATH", "container": "/mtp-draft", "mode": "ro"}, diff --git a/scripts/config/glm53-flash-e10536a-dflash2-bf16-sparkcache-tp4-dcp1.example.json b/scripts/config/glm53-flash-e10536a-dflash2-bf16-sparkcache-tp4-dcp1.example.json index 75bb2d84..c0a11543 100644 --- a/scripts/config/glm53-flash-e10536a-dflash2-bf16-sparkcache-tp4-dcp1.example.json +++ b/scripts/config/glm53-flash-e10536a-dflash2-bf16-sparkcache-tp4-dcp1.example.json @@ -81,7 +81,7 @@ "--async-scheduling", "--enable-prefix-caching", "--cudagraph-metrics", - "--kv-transfer-config", "{\"kv_connector\":\"SparkContextCacheConnector\",\"kv_connector_module_path\":\"sparkcache.spark_context_cache_connector\",\"kv_role\":\"kv_both\",\"kv_load_failure_policy\":\"recompute\",\"kv_connector_extra_config\":{\"spark_cache_root\":\"/cache/jit/sparkcache-context\",\"spark_cache_model_profile\":\"glm53-flash-hybrid\",\"spark_cache_target_checkpoint_sha256\":\"a35e6bf2875c1875609b8deaec404c07c6cc80259e4222fc0b51e649498bd6b9\",\"spark_cache_draft_checkpoint_sha256\":\"b33c03475ba7322cf398828f2d8d1be376df30dc05c6b40c28c8ea8da23e410b\",\"spark_cache_draft_policy\":\"separate\",\"spark_cache_store\":true,\"spark_cache_restore\":true,\"spark_cache_scheduler_probe\":\"none\",\"spark_cache_streaming_snapshots\":false,\"spark_cache_native_restore\":true,\"spark_cache_max_bytes\":51539607552,\"spark_cache_low_watermark_bytes\":42949672960,\"spark_cache_ttl_seconds\":0,\"spark_cache_min_span_tokens\":4096,\"spark_cache_max_span_tokens\":524288,\"spark_cache_native_library\":\"/opt/sparkcache-src/sparkcache/native/build-cuda/libspark_cache_placement.so\",\"spark_cache_native_library_sha256\":\"REPLACE_WITH_NATIVE_LIBRARY_SHA256\",\"spark_cache_native_arena_bytes\":268435456,\"spark_cache_native_io_workers\":8,\"spark_cache_load_threads\":2,\"spark_cache_clear_once\":\"sparkring-e10536a-dflash5-initialization\"}}" + "--kv-transfer-config", "{\"kv_connector\":\"SparkContextCacheConnector\",\"kv_connector_module_path\":\"sparkcache.spark_context_cache_connector\",\"kv_role\":\"kv_both\",\"kv_load_failure_policy\":\"recompute\",\"kv_connector_extra_config\":{\"spark_cache_root\":\"/cache/jit/sparkcache-context\",\"spark_cache_model_profile\":\"glm53-flash-hybrid\",\"spark_cache_target_checkpoint_sha256\":\"a35e6bf2875c1875609b8deaec404c07c6cc80259e4222fc0b51e649498bd6b9\",\"spark_cache_draft_checkpoint_sha256\":\"b33c03475ba7322cf398828f2d8d1be376df30dc05c6b40c28c8ea8da23e410b\",\"spark_cache_draft_policy\":\"separate\",\"spark_cache_store\":true,\"spark_cache_restore\":true,\"spark_cache_scheduler_probe\":\"none\",\"spark_cache_streaming_snapshots\":false,\"spark_cache_cuda_restore\":true,\"spark_cache_max_bytes\":51539607552,\"spark_cache_low_watermark_bytes\":42949672960,\"spark_cache_ttl_seconds\":0,\"spark_cache_min_span_tokens\":4096,\"spark_cache_max_span_tokens\":524288,\"spark_cache_cuda_placement_library\":\"/opt/sparkcache-src/sparkcache/native/build-cuda/libspark_cache_placement.so\",\"spark_cache_cuda_placement_library_sha256\":\"REPLACE_WITH_CUDA_PLACEMENT_LIBRARY_SHA256\",\"spark_cache_cuda_placement_arena_bytes\":268435456,\"spark_cache_cuda_restore_io_workers\":8,\"spark_cache_load_threads\":2,\"spark_cache_clear_once\":\"sparkring-e10536a-dflash5-initialization\"}}" ], "extra_volumes": [ {"host": "/REPLACE/DFLASH_MODEL_HOST_PATH", "container": "/mtp-draft", "mode": "ro"}, @@ -127,6 +127,6 @@ "attestation_hook": [ "/bin/sh", "-ec", - "test -f /models/target/config.json && test -f /models/target/model.safetensors.index.json && test \"$(sha256sum /models/target/config.json | cut -d ' ' -f1)\" = 676382abd1e90a6c85f0c8f33d45441ecd45fd514fd7b63ce5610e732d8e4996 && test \"$(sha256sum /models/target/model.safetensors.index.json | cut -d ' ' -f1)\" = 0d1d9e6b226e76520e182de10d4e7194cc885c5cb1bf885bb90de1916ce312cb && test -f /mtp-draft/config.json && test -f /mtp-draft/model.safetensors && test \"$(sha256sum /mtp-draft/config.json | cut -d ' ' -f1)\" = c4aeac0101196a6e26705b34c45230bcd0c7c68ee2d2d1efdb242087f3712573 && test \"$(sha256sum /mtp-draft/model.safetensors | cut -d ' ' -f1)\" = b33c03475ba7322cf398828f2d8d1be376df30dc05c6b40c28c8ea8da23e410b && test \"$(sha256sum /opt/sparkring/nccl/libnccl.so.2 | cut -d ' ' -f1)\" = 5f1c3f10d5ace66d4ba584415bbfe42b6ac1a0a9116a3b81dcbe50516ad924b3 && test \"$(python3 -c 'import importlib.util, pathlib; spec = importlib.util.spec_from_file_location(\"source_identity\", \"/opt/sparkcache-source-identity.py\"); module = importlib.util.module_from_spec(spec); spec.loader.exec_module(module); print(module.source_tree_sha256(pathlib.Path(\"/opt/sparkcache-src/sparkcache\")))')\" = 34108fb22ba95b457bf4b357407b176dcbf3a6db6227227b21ecee045502a16f && test \"$(sha256sum /opt/sparkcache-src/sparkcache/runtime_patches/vllm-kv-block-lease-contract-e10536a.json | cut -d ' ' -f1)\" = 70cd4e923d049da96bcfa4a5b460e2ff5f7460881d5cfd0621607080fd70f68f && test \"$(sha256sum /usr/local/lib/python3.12/dist-packages/vllm/config/vllm.py | cut -d ' ' -f1)\" = 852c2277bd99858cf940238856e7b7f51e9011f768d3c704c930bb9deaf73f61 && test \"$(sha256sum /opt/sparkcache-src/sparkcache/native/build-cuda/libspark_cache_placement.so | cut -d ' ' -f1)\" = REPLACE_WITH_NATIVE_LIBRARY_SHA256 && python3 /opt/sparkcache-src/sparkcache/runtime_patches/verify_lease_contract.py --vllm-root /usr/local/lib/python3.12/dist-packages --contract /opt/sparkcache-src/sparkcache/runtime_patches/vllm-kv-block-lease-contract-e10536a.json" + "test -f /models/target/config.json && test -f /models/target/model.safetensors.index.json && test \"$(sha256sum /models/target/config.json | cut -d ' ' -f1)\" = 676382abd1e90a6c85f0c8f33d45441ecd45fd514fd7b63ce5610e732d8e4996 && test \"$(sha256sum /models/target/model.safetensors.index.json | cut -d ' ' -f1)\" = 0d1d9e6b226e76520e182de10d4e7194cc885c5cb1bf885bb90de1916ce312cb && test -f /mtp-draft/config.json && test -f /mtp-draft/model.safetensors && test \"$(sha256sum /mtp-draft/config.json | cut -d ' ' -f1)\" = c4aeac0101196a6e26705b34c45230bcd0c7c68ee2d2d1efdb242087f3712573 && test \"$(sha256sum /mtp-draft/model.safetensors | cut -d ' ' -f1)\" = b33c03475ba7322cf398828f2d8d1be376df30dc05c6b40c28c8ea8da23e410b && test \"$(sha256sum /opt/sparkring/nccl/libnccl.so.2 | cut -d ' ' -f1)\" = 5f1c3f10d5ace66d4ba584415bbfe42b6ac1a0a9116a3b81dcbe50516ad924b3 && test \"$(python3 -c 'import importlib.util, pathlib; spec = importlib.util.spec_from_file_location(\"source_identity\", \"/opt/sparkcache-source-identity.py\"); module = importlib.util.module_from_spec(spec); spec.loader.exec_module(module); print(module.source_tree_sha256(pathlib.Path(\"/opt/sparkcache-src/sparkcache\")))')\" = 34108fb22ba95b457bf4b357407b176dcbf3a6db6227227b21ecee045502a16f && test \"$(sha256sum /opt/sparkcache-src/sparkcache/runtime_patches/vllm-kv-block-lease-contract-e10536a.json | cut -d ' ' -f1)\" = 70cd4e923d049da96bcfa4a5b460e2ff5f7460881d5cfd0621607080fd70f68f && test \"$(sha256sum /usr/local/lib/python3.12/dist-packages/vllm/config/vllm.py | cut -d ' ' -f1)\" = 852c2277bd99858cf940238856e7b7f51e9011f768d3c704c930bb9deaf73f61 && test \"$(sha256sum /opt/sparkcache-src/sparkcache/native/build-cuda/libspark_cache_placement.so | cut -d ' ' -f1)\" = REPLACE_WITH_CUDA_PLACEMENT_LIBRARY_SHA256 && python3 /opt/sparkcache-src/sparkcache/runtime_patches/verify_lease_contract.py --vllm-root /usr/local/lib/python3.12/dist-packages --contract /opt/sparkcache-src/sparkcache/runtime_patches/vllm-kv-block-lease-contract-e10536a.json" ] } diff --git a/scripts/config/glm53-flash-e10536a-mtp5-adaptive-fastsafetensors-sparkcache-tp4-dcp1.example.json b/scripts/config/glm53-flash-e10536a-mtp5-adaptive-fastsafetensors-sparkcache-tp4-dcp1.example.json index 0b878fb7..9b2822f0 100644 --- a/scripts/config/glm53-flash-e10536a-mtp5-adaptive-fastsafetensors-sparkcache-tp4-dcp1.example.json +++ b/scripts/config/glm53-flash-e10536a-mtp5-adaptive-fastsafetensors-sparkcache-tp4-dcp1.example.json @@ -82,7 +82,7 @@ "--async-scheduling", "--enable-prefix-caching", "--cudagraph-metrics", - "--kv-transfer-config", "{\"kv_connector\":\"SparkContextCacheConnector\",\"kv_connector_module_path\":\"sparkcache.spark_context_cache_connector\",\"kv_role\":\"kv_both\",\"kv_load_failure_policy\":\"recompute\",\"kv_connector_extra_config\":{\"spark_cache_root\":\"/cache/jit/sparkcache-context\",\"spark_cache_model_profile\":\"glm53-flash-hybrid\",\"spark_cache_target_checkpoint_sha256\":\"a35e6bf2875c1875609b8deaec404c07c6cc80259e4222fc0b51e649498bd6b9\",\"spark_cache_draft_checkpoint_sha256\":\"915b7f71f16348ab84f138d975f3db90f3edfca69976e1b9ffcc03f21cf5eb22\",\"spark_cache_draft_policy\":\"separate\",\"spark_cache_store\":true,\"spark_cache_restore\":true,\"spark_cache_scheduler_probe\":\"none\",\"spark_cache_streaming_snapshots\":false,\"spark_cache_native_restore\":true,\"spark_cache_max_bytes\":51539607552,\"spark_cache_low_watermark_bytes\":42949672960,\"spark_cache_ttl_seconds\":0,\"spark_cache_min_span_tokens\":4096,\"spark_cache_max_span_tokens\":524288,\"spark_cache_native_library\":\"/opt/sparkcache-src/sparkcache/native/build-cuda/libspark_cache_placement.so\",\"spark_cache_native_library_sha256\":\"REPLACE_WITH_NATIVE_LIBRARY_SHA256\",\"spark_cache_native_arena_bytes\":268435456,\"spark_cache_native_io_workers\":8,\"spark_cache_load_threads\":2,\"spark_cache_clear_once\":\"sparkring-e10536a-mtp5-adaptive-fastsafetensors-initialization\"}}" + "--kv-transfer-config", "{\"kv_connector\":\"SparkContextCacheConnector\",\"kv_connector_module_path\":\"sparkcache.spark_context_cache_connector\",\"kv_role\":\"kv_both\",\"kv_load_failure_policy\":\"recompute\",\"kv_connector_extra_config\":{\"spark_cache_root\":\"/cache/jit/sparkcache-context\",\"spark_cache_model_profile\":\"glm53-flash-hybrid\",\"spark_cache_target_checkpoint_sha256\":\"a35e6bf2875c1875609b8deaec404c07c6cc80259e4222fc0b51e649498bd6b9\",\"spark_cache_draft_checkpoint_sha256\":\"915b7f71f16348ab84f138d975f3db90f3edfca69976e1b9ffcc03f21cf5eb22\",\"spark_cache_draft_policy\":\"separate\",\"spark_cache_store\":true,\"spark_cache_restore\":true,\"spark_cache_scheduler_probe\":\"none\",\"spark_cache_streaming_snapshots\":false,\"spark_cache_cuda_restore\":true,\"spark_cache_max_bytes\":51539607552,\"spark_cache_low_watermark_bytes\":42949672960,\"spark_cache_ttl_seconds\":0,\"spark_cache_min_span_tokens\":4096,\"spark_cache_max_span_tokens\":524288,\"spark_cache_cuda_placement_library\":\"/opt/sparkcache-src/sparkcache/native/build-cuda/libspark_cache_placement.so\",\"spark_cache_cuda_placement_library_sha256\":\"REPLACE_WITH_CUDA_PLACEMENT_LIBRARY_SHA256\",\"spark_cache_cuda_placement_arena_bytes\":268435456,\"spark_cache_cuda_restore_io_workers\":8,\"spark_cache_load_threads\":2,\"spark_cache_clear_once\":\"sparkring-e10536a-mtp5-adaptive-fastsafetensors-initialization\"}}" ], "extra_volumes": [ {"host": "/REPLACE/GLM53_CACHE_HOST_ROOT", "container": "/cache/jit", "mode": "rw"} @@ -129,6 +129,6 @@ "attestation_hook": [ "/bin/sh", "-ec", - "test -f /models/target/config.json && test -f /models/target/model.safetensors.index.json && test \"$(sha256sum /models/target/config.json | cut -d ' ' -f1)\" = 676382abd1e90a6c85f0c8f33d45441ecd45fd514fd7b63ce5610e732d8e4996 && test \"$(sha256sum /models/target/model.safetensors.index.json | cut -d ' ' -f1)\" = 0d1d9e6b226e76520e182de10d4e7194cc885c5cb1bf885bb90de1916ce312cb && test \"$(sha256sum /opt/sparkring/nccl/libnccl.so.2 | cut -d ' ' -f1)\" = 5f1c3f10d5ace66d4ba584415bbfe42b6ac1a0a9116a3b81dcbe50516ad924b3 && test \"$(python3 -c 'import importlib.util, pathlib; spec = importlib.util.spec_from_file_location(\"source_identity\", \"/opt/sparkcache-source-identity.py\"); module = importlib.util.module_from_spec(spec); spec.loader.exec_module(module); print(module.source_tree_sha256(pathlib.Path(\"/opt/sparkcache-src/sparkcache\")))')\" = 34108fb22ba95b457bf4b357407b176dcbf3a6db6227227b21ecee045502a16f && test \"$(sha256sum /opt/sparkcache-src/sparkcache/runtime_patches/vllm-kv-block-lease-contract-e10536a.json | cut -d ' ' -f1)\" = 70cd4e923d049da96bcfa4a5b460e2ff5f7460881d5cfd0621607080fd70f68f && test \"$(sha256sum /usr/local/lib/python3.12/dist-packages/vllm/config/vllm.py | cut -d ' ' -f1)\" = 852c2277bd99858cf940238856e7b7f51e9011f768d3c704c930bb9deaf73f61 && test \"$(sha256sum /opt/sparkcache-src/sparkcache/native/build-cuda/libspark_cache_placement.so | cut -d ' ' -f1)\" = REPLACE_WITH_NATIVE_LIBRARY_SHA256 && python3 /opt/sparkcache-src/sparkcache/runtime_patches/verify_lease_contract.py --vllm-root /usr/local/lib/python3.12/dist-packages --contract /opt/sparkcache-src/sparkcache/runtime_patches/vllm-kv-block-lease-contract-e10536a.json" + "test -f /models/target/config.json && test -f /models/target/model.safetensors.index.json && test \"$(sha256sum /models/target/config.json | cut -d ' ' -f1)\" = 676382abd1e90a6c85f0c8f33d45441ecd45fd514fd7b63ce5610e732d8e4996 && test \"$(sha256sum /models/target/model.safetensors.index.json | cut -d ' ' -f1)\" = 0d1d9e6b226e76520e182de10d4e7194cc885c5cb1bf885bb90de1916ce312cb && test \"$(sha256sum /opt/sparkring/nccl/libnccl.so.2 | cut -d ' ' -f1)\" = 5f1c3f10d5ace66d4ba584415bbfe42b6ac1a0a9116a3b81dcbe50516ad924b3 && test \"$(python3 -c 'import importlib.util, pathlib; spec = importlib.util.spec_from_file_location(\"source_identity\", \"/opt/sparkcache-source-identity.py\"); module = importlib.util.module_from_spec(spec); spec.loader.exec_module(module); print(module.source_tree_sha256(pathlib.Path(\"/opt/sparkcache-src/sparkcache\")))')\" = 34108fb22ba95b457bf4b357407b176dcbf3a6db6227227b21ecee045502a16f && test \"$(sha256sum /opt/sparkcache-src/sparkcache/runtime_patches/vllm-kv-block-lease-contract-e10536a.json | cut -d ' ' -f1)\" = 70cd4e923d049da96bcfa4a5b460e2ff5f7460881d5cfd0621607080fd70f68f && test \"$(sha256sum /usr/local/lib/python3.12/dist-packages/vllm/config/vllm.py | cut -d ' ' -f1)\" = 852c2277bd99858cf940238856e7b7f51e9011f768d3c704c930bb9deaf73f61 && test \"$(sha256sum /opt/sparkcache-src/sparkcache/native/build-cuda/libspark_cache_placement.so | cut -d ' ' -f1)\" = REPLACE_WITH_CUDA_PLACEMENT_LIBRARY_SHA256 && python3 /opt/sparkcache-src/sparkcache/runtime_patches/verify_lease_contract.py --vllm-root /usr/local/lib/python3.12/dist-packages --contract /opt/sparkcache-src/sparkcache/runtime_patches/vllm-kv-block-lease-contract-e10536a.json" ] } diff --git a/scripts/config/glm53-flash-e10536a-mtp5-adaptive-sparkcache-tp4-dcp1.example.json b/scripts/config/glm53-flash-e10536a-mtp5-adaptive-sparkcache-tp4-dcp1.example.json index 1b7162d8..359f9d10 100644 --- a/scripts/config/glm53-flash-e10536a-mtp5-adaptive-sparkcache-tp4-dcp1.example.json +++ b/scripts/config/glm53-flash-e10536a-mtp5-adaptive-sparkcache-tp4-dcp1.example.json @@ -81,7 +81,7 @@ "--async-scheduling", "--enable-prefix-caching", "--cudagraph-metrics", - "--kv-transfer-config", "{\"kv_connector\":\"SparkContextCacheConnector\",\"kv_connector_module_path\":\"sparkcache.spark_context_cache_connector\",\"kv_role\":\"kv_both\",\"kv_load_failure_policy\":\"recompute\",\"kv_connector_extra_config\":{\"spark_cache_root\":\"/cache/jit/sparkcache-context\",\"spark_cache_model_profile\":\"glm53-flash-hybrid\",\"spark_cache_target_checkpoint_sha256\":\"a35e6bf2875c1875609b8deaec404c07c6cc80259e4222fc0b51e649498bd6b9\",\"spark_cache_draft_checkpoint_sha256\":\"915b7f71f16348ab84f138d975f3db90f3edfca69976e1b9ffcc03f21cf5eb22\",\"spark_cache_draft_policy\":\"separate\",\"spark_cache_store\":true,\"spark_cache_restore\":true,\"spark_cache_scheduler_probe\":\"none\",\"spark_cache_streaming_snapshots\":false,\"spark_cache_native_restore\":true,\"spark_cache_max_bytes\":51539607552,\"spark_cache_low_watermark_bytes\":42949672960,\"spark_cache_ttl_seconds\":0,\"spark_cache_min_span_tokens\":4096,\"spark_cache_max_span_tokens\":524288,\"spark_cache_native_library\":\"/opt/sparkcache-src/sparkcache/native/build-cuda/libspark_cache_placement.so\",\"spark_cache_native_library_sha256\":\"REPLACE_WITH_NATIVE_LIBRARY_SHA256\",\"spark_cache_native_arena_bytes\":268435456,\"spark_cache_native_io_workers\":8,\"spark_cache_load_threads\":2,\"spark_cache_clear_once\":\"sparkring-e10536a-mtp5-adaptive-initialization\"}}" + "--kv-transfer-config", "{\"kv_connector\":\"SparkContextCacheConnector\",\"kv_connector_module_path\":\"sparkcache.spark_context_cache_connector\",\"kv_role\":\"kv_both\",\"kv_load_failure_policy\":\"recompute\",\"kv_connector_extra_config\":{\"spark_cache_root\":\"/cache/jit/sparkcache-context\",\"spark_cache_model_profile\":\"glm53-flash-hybrid\",\"spark_cache_target_checkpoint_sha256\":\"a35e6bf2875c1875609b8deaec404c07c6cc80259e4222fc0b51e649498bd6b9\",\"spark_cache_draft_checkpoint_sha256\":\"915b7f71f16348ab84f138d975f3db90f3edfca69976e1b9ffcc03f21cf5eb22\",\"spark_cache_draft_policy\":\"separate\",\"spark_cache_store\":true,\"spark_cache_restore\":true,\"spark_cache_scheduler_probe\":\"none\",\"spark_cache_streaming_snapshots\":false,\"spark_cache_cuda_restore\":true,\"spark_cache_max_bytes\":51539607552,\"spark_cache_low_watermark_bytes\":42949672960,\"spark_cache_ttl_seconds\":0,\"spark_cache_min_span_tokens\":4096,\"spark_cache_max_span_tokens\":524288,\"spark_cache_cuda_placement_library\":\"/opt/sparkcache-src/sparkcache/native/build-cuda/libspark_cache_placement.so\",\"spark_cache_cuda_placement_library_sha256\":\"REPLACE_WITH_CUDA_PLACEMENT_LIBRARY_SHA256\",\"spark_cache_cuda_placement_arena_bytes\":268435456,\"spark_cache_cuda_restore_io_workers\":8,\"spark_cache_load_threads\":2,\"spark_cache_clear_once\":\"sparkring-e10536a-mtp5-adaptive-initialization\"}}" ], "extra_volumes": [ {"host": "/REPLACE/GLM53_CACHE_HOST_ROOT", "container": "/cache/jit", "mode": "rw"} @@ -128,6 +128,6 @@ "attestation_hook": [ "/bin/sh", "-ec", - "test -f /models/target/config.json && test -f /models/target/model.safetensors.index.json && test \"$(sha256sum /models/target/config.json | cut -d ' ' -f1)\" = 676382abd1e90a6c85f0c8f33d45441ecd45fd514fd7b63ce5610e732d8e4996 && test \"$(sha256sum /models/target/model.safetensors.index.json | cut -d ' ' -f1)\" = 0d1d9e6b226e76520e182de10d4e7194cc885c5cb1bf885bb90de1916ce312cb && test \"$(sha256sum /opt/sparkring/nccl/libnccl.so.2 | cut -d ' ' -f1)\" = 5f1c3f10d5ace66d4ba584415bbfe42b6ac1a0a9116a3b81dcbe50516ad924b3 && test \"$(python3 -c 'import importlib.util, pathlib; spec = importlib.util.spec_from_file_location(\"source_identity\", \"/opt/sparkcache-source-identity.py\"); module = importlib.util.module_from_spec(spec); spec.loader.exec_module(module); print(module.source_tree_sha256(pathlib.Path(\"/opt/sparkcache-src/sparkcache\")))')\" = 34108fb22ba95b457bf4b357407b176dcbf3a6db6227227b21ecee045502a16f && test \"$(sha256sum /opt/sparkcache-src/sparkcache/runtime_patches/vllm-kv-block-lease-contract-e10536a.json | cut -d ' ' -f1)\" = 70cd4e923d049da96bcfa4a5b460e2ff5f7460881d5cfd0621607080fd70f68f && test \"$(sha256sum /usr/local/lib/python3.12/dist-packages/vllm/config/vllm.py | cut -d ' ' -f1)\" = 852c2277bd99858cf940238856e7b7f51e9011f768d3c704c930bb9deaf73f61 && test \"$(sha256sum /opt/sparkcache-src/sparkcache/native/build-cuda/libspark_cache_placement.so | cut -d ' ' -f1)\" = REPLACE_WITH_NATIVE_LIBRARY_SHA256 && python3 /opt/sparkcache-src/sparkcache/runtime_patches/verify_lease_contract.py --vllm-root /usr/local/lib/python3.12/dist-packages --contract /opt/sparkcache-src/sparkcache/runtime_patches/vllm-kv-block-lease-contract-e10536a.json" + "test -f /models/target/config.json && test -f /models/target/model.safetensors.index.json && test \"$(sha256sum /models/target/config.json | cut -d ' ' -f1)\" = 676382abd1e90a6c85f0c8f33d45441ecd45fd514fd7b63ce5610e732d8e4996 && test \"$(sha256sum /models/target/model.safetensors.index.json | cut -d ' ' -f1)\" = 0d1d9e6b226e76520e182de10d4e7194cc885c5cb1bf885bb90de1916ce312cb && test \"$(sha256sum /opt/sparkring/nccl/libnccl.so.2 | cut -d ' ' -f1)\" = 5f1c3f10d5ace66d4ba584415bbfe42b6ac1a0a9116a3b81dcbe50516ad924b3 && test \"$(python3 -c 'import importlib.util, pathlib; spec = importlib.util.spec_from_file_location(\"source_identity\", \"/opt/sparkcache-source-identity.py\"); module = importlib.util.module_from_spec(spec); spec.loader.exec_module(module); print(module.source_tree_sha256(pathlib.Path(\"/opt/sparkcache-src/sparkcache\")))')\" = 34108fb22ba95b457bf4b357407b176dcbf3a6db6227227b21ecee045502a16f && test \"$(sha256sum /opt/sparkcache-src/sparkcache/runtime_patches/vllm-kv-block-lease-contract-e10536a.json | cut -d ' ' -f1)\" = 70cd4e923d049da96bcfa4a5b460e2ff5f7460881d5cfd0621607080fd70f68f && test \"$(sha256sum /usr/local/lib/python3.12/dist-packages/vllm/config/vllm.py | cut -d ' ' -f1)\" = 852c2277bd99858cf940238856e7b7f51e9011f768d3c704c930bb9deaf73f61 && test \"$(sha256sum /opt/sparkcache-src/sparkcache/native/build-cuda/libspark_cache_placement.so | cut -d ' ' -f1)\" = REPLACE_WITH_CUDA_PLACEMENT_LIBRARY_SHA256 && python3 /opt/sparkcache-src/sparkcache/runtime_patches/verify_lease_contract.py --vllm-root /usr/local/lib/python3.12/dist-packages --contract /opt/sparkcache-src/sparkcache/runtime_patches/vllm-kv-block-lease-contract-e10536a.json" ] } diff --git a/scripts/config/glm53-flash-e10536a-mtp5-sparkcache-tp4-dcp1.example.json b/scripts/config/glm53-flash-e10536a-mtp5-sparkcache-tp4-dcp1.example.json index 4138edc6..82519fd3 100644 --- a/scripts/config/glm53-flash-e10536a-mtp5-sparkcache-tp4-dcp1.example.json +++ b/scripts/config/glm53-flash-e10536a-mtp5-sparkcache-tp4-dcp1.example.json @@ -81,7 +81,7 @@ "--async-scheduling", "--enable-prefix-caching", "--cudagraph-metrics", - "--kv-transfer-config", "{\"kv_connector\":\"SparkContextCacheConnector\",\"kv_connector_module_path\":\"sparkcache.spark_context_cache_connector\",\"kv_role\":\"kv_both\",\"kv_load_failure_policy\":\"recompute\",\"kv_connector_extra_config\":{\"spark_cache_root\":\"/cache/jit/sparkcache-context\",\"spark_cache_model_profile\":\"glm53-flash-hybrid\",\"spark_cache_target_checkpoint_sha256\":\"a35e6bf2875c1875609b8deaec404c07c6cc80259e4222fc0b51e649498bd6b9\",\"spark_cache_draft_checkpoint_sha256\":\"e21f4a0ef4e53c995b7b003e05d8b028abf262b88203ab8b8bc21d6a44462dd6\",\"spark_cache_draft_policy\":\"separate\",\"spark_cache_store\":true,\"spark_cache_restore\":true,\"spark_cache_scheduler_probe\":\"none\",\"spark_cache_streaming_snapshots\":false,\"spark_cache_native_restore\":true,\"spark_cache_max_bytes\":51539607552,\"spark_cache_low_watermark_bytes\":42949672960,\"spark_cache_ttl_seconds\":0,\"spark_cache_min_span_tokens\":4096,\"spark_cache_max_span_tokens\":524288,\"spark_cache_native_library\":\"/opt/sparkcache-src/sparkcache/native/build-cuda/libspark_cache_placement.so\",\"spark_cache_native_library_sha256\":\"REPLACE_WITH_NATIVE_LIBRARY_SHA256\",\"spark_cache_native_arena_bytes\":268435456,\"spark_cache_native_io_workers\":8,\"spark_cache_load_threads\":2,\"spark_cache_clear_once\":\"sparkring-e10536a-mtp5-static-initialization\"}}" + "--kv-transfer-config", "{\"kv_connector\":\"SparkContextCacheConnector\",\"kv_connector_module_path\":\"sparkcache.spark_context_cache_connector\",\"kv_role\":\"kv_both\",\"kv_load_failure_policy\":\"recompute\",\"kv_connector_extra_config\":{\"spark_cache_root\":\"/cache/jit/sparkcache-context\",\"spark_cache_model_profile\":\"glm53-flash-hybrid\",\"spark_cache_target_checkpoint_sha256\":\"a35e6bf2875c1875609b8deaec404c07c6cc80259e4222fc0b51e649498bd6b9\",\"spark_cache_draft_checkpoint_sha256\":\"e21f4a0ef4e53c995b7b003e05d8b028abf262b88203ab8b8bc21d6a44462dd6\",\"spark_cache_draft_policy\":\"separate\",\"spark_cache_store\":true,\"spark_cache_restore\":true,\"spark_cache_scheduler_probe\":\"none\",\"spark_cache_streaming_snapshots\":false,\"spark_cache_cuda_restore\":true,\"spark_cache_max_bytes\":51539607552,\"spark_cache_low_watermark_bytes\":42949672960,\"spark_cache_ttl_seconds\":0,\"spark_cache_min_span_tokens\":4096,\"spark_cache_max_span_tokens\":524288,\"spark_cache_cuda_placement_library\":\"/opt/sparkcache-src/sparkcache/native/build-cuda/libspark_cache_placement.so\",\"spark_cache_cuda_placement_library_sha256\":\"REPLACE_WITH_CUDA_PLACEMENT_LIBRARY_SHA256\",\"spark_cache_cuda_placement_arena_bytes\":268435456,\"spark_cache_cuda_restore_io_workers\":8,\"spark_cache_load_threads\":2,\"spark_cache_clear_once\":\"sparkring-e10536a-mtp5-static-initialization\"}}" ], "extra_volumes": [ {"host": "/REPLACE/GLM53_CACHE_HOST_ROOT", "container": "/cache/jit", "mode": "rw"} @@ -126,6 +126,6 @@ "attestation_hook": [ "/bin/sh", "-ec", - "test -f /models/target/config.json && test -f /models/target/model.safetensors.index.json && test \"$(sha256sum /models/target/config.json | cut -d ' ' -f1)\" = 676382abd1e90a6c85f0c8f33d45441ecd45fd514fd7b63ce5610e732d8e4996 && test \"$(sha256sum /models/target/model.safetensors.index.json | cut -d ' ' -f1)\" = 0d1d9e6b226e76520e182de10d4e7194cc885c5cb1bf885bb90de1916ce312cb && test \"$(sha256sum /opt/sparkring/nccl/libnccl.so.2 | cut -d ' ' -f1)\" = 5f1c3f10d5ace66d4ba584415bbfe42b6ac1a0a9116a3b81dcbe50516ad924b3 && test \"$(python3 -c 'import importlib.util, pathlib; spec = importlib.util.spec_from_file_location(\"source_identity\", \"/opt/sparkcache-source-identity.py\"); module = importlib.util.module_from_spec(spec); spec.loader.exec_module(module); print(module.source_tree_sha256(pathlib.Path(\"/opt/sparkcache-src/sparkcache\")))')\" = 34108fb22ba95b457bf4b357407b176dcbf3a6db6227227b21ecee045502a16f && test \"$(sha256sum /opt/sparkcache-src/sparkcache/runtime_patches/vllm-kv-block-lease-contract-e10536a.json | cut -d ' ' -f1)\" = 70cd4e923d049da96bcfa4a5b460e2ff5f7460881d5cfd0621607080fd70f68f && test \"$(sha256sum /usr/local/lib/python3.12/dist-packages/vllm/config/vllm.py | cut -d ' ' -f1)\" = 852c2277bd99858cf940238856e7b7f51e9011f768d3c704c930bb9deaf73f61 && test \"$(sha256sum /opt/sparkcache-src/sparkcache/native/build-cuda/libspark_cache_placement.so | cut -d ' ' -f1)\" = REPLACE_WITH_NATIVE_LIBRARY_SHA256 && python3 /opt/sparkcache-src/sparkcache/runtime_patches/verify_lease_contract.py --vllm-root /usr/local/lib/python3.12/dist-packages --contract /opt/sparkcache-src/sparkcache/runtime_patches/vllm-kv-block-lease-contract-e10536a.json" + "test -f /models/target/config.json && test -f /models/target/model.safetensors.index.json && test \"$(sha256sum /models/target/config.json | cut -d ' ' -f1)\" = 676382abd1e90a6c85f0c8f33d45441ecd45fd514fd7b63ce5610e732d8e4996 && test \"$(sha256sum /models/target/model.safetensors.index.json | cut -d ' ' -f1)\" = 0d1d9e6b226e76520e182de10d4e7194cc885c5cb1bf885bb90de1916ce312cb && test \"$(sha256sum /opt/sparkring/nccl/libnccl.so.2 | cut -d ' ' -f1)\" = 5f1c3f10d5ace66d4ba584415bbfe42b6ac1a0a9116a3b81dcbe50516ad924b3 && test \"$(python3 -c 'import importlib.util, pathlib; spec = importlib.util.spec_from_file_location(\"source_identity\", \"/opt/sparkcache-source-identity.py\"); module = importlib.util.module_from_spec(spec); spec.loader.exec_module(module); print(module.source_tree_sha256(pathlib.Path(\"/opt/sparkcache-src/sparkcache\")))')\" = 34108fb22ba95b457bf4b357407b176dcbf3a6db6227227b21ecee045502a16f && test \"$(sha256sum /opt/sparkcache-src/sparkcache/runtime_patches/vllm-kv-block-lease-contract-e10536a.json | cut -d ' ' -f1)\" = 70cd4e923d049da96bcfa4a5b460e2ff5f7460881d5cfd0621607080fd70f68f && test \"$(sha256sum /usr/local/lib/python3.12/dist-packages/vllm/config/vllm.py | cut -d ' ' -f1)\" = 852c2277bd99858cf940238856e7b7f51e9011f768d3c704c930bb9deaf73f61 && test \"$(sha256sum /opt/sparkcache-src/sparkcache/native/build-cuda/libspark_cache_placement.so | cut -d ' ' -f1)\" = REPLACE_WITH_CUDA_PLACEMENT_LIBRARY_SHA256 && python3 /opt/sparkcache-src/sparkcache/runtime_patches/verify_lease_contract.py --vllm-root /usr/local/lib/python3.12/dist-packages --contract /opt/sparkcache-src/sparkcache/runtime_patches/vllm-kv-block-lease-contract-e10536a.json" ] } diff --git a/scripts/config/glm53-flash-public-python-overlay-mtp5-adaptive-fastsafetensors-sparkcache-tp4-dcp1.example.json b/scripts/config/glm53-flash-public-python-overlay-mtp5-adaptive-fastsafetensors-sparkcache-tp4-dcp1.example.json index 89911885..95b069ff 100644 --- a/scripts/config/glm53-flash-public-python-overlay-mtp5-adaptive-fastsafetensors-sparkcache-tp4-dcp1.example.json +++ b/scripts/config/glm53-flash-public-python-overlay-mtp5-adaptive-fastsafetensors-sparkcache-tp4-dcp1.example.json @@ -82,7 +82,7 @@ "--async-scheduling", "--enable-prefix-caching", "--cudagraph-metrics", - "--kv-transfer-config", "{\"kv_connector\":\"SparkContextCacheConnector\",\"kv_connector_module_path\":\"sparkcache.spark_context_cache_connector\",\"kv_role\":\"kv_both\",\"kv_load_failure_policy\":\"recompute\",\"kv_connector_extra_config\":{\"spark_cache_root\":\"/cache/jit/sparkcache-context/vllm-py-0b67266-native-da4d7be-b12x-b1d541f-tail-cow\",\"spark_cache_model_profile\":\"glm53-flash-hybrid\",\"spark_cache_publication_schema\":\"tail-cow-v1\",\"spark_cache_target_checkpoint_sha256\":\"a35e6bf2875c1875609b8deaec404c07c6cc80259e4222fc0b51e649498bd6b9\",\"spark_cache_draft_checkpoint_sha256\":\"2e06d909ce5bb71c0c0e3e8be74a70e3b41d92ba4c30196cfb0957fb812acef6\",\"spark_cache_draft_policy\":\"separate\",\"spark_cache_store\":true,\"spark_cache_restore\":true,\"spark_cache_scheduler_probe\":\"none\",\"spark_cache_streaming_snapshots\":false,\"spark_cache_native_restore\":true,\"spark_cache_max_bytes\":51539607552,\"spark_cache_low_watermark_bytes\":42949672960,\"spark_cache_ttl_seconds\":0,\"spark_cache_min_span_tokens\":4096,\"spark_cache_max_span_tokens\":524288,\"spark_cache_native_library\":\"/opt/sparkcache-src/sparkcache/native/build-cuda/libspark_cache_placement.so\",\"spark_cache_native_library_sha256\":\"REPLACE_WITH_NATIVE_LIBRARY_SHA256\",\"spark_cache_native_arena_bytes\":268435456,\"spark_cache_native_io_workers\":8,\"spark_cache_load_threads\":2,\"spark_cache_clear_once\":\"sparkring-vllm-python-0b67266-native-da4d7be-b12x-b1d541f-tail-cow-initialization\"}}" + "--kv-transfer-config", "{\"kv_connector\":\"SparkContextCacheConnector\",\"kv_connector_module_path\":\"sparkcache.spark_context_cache_connector\",\"kv_role\":\"kv_both\",\"kv_load_failure_policy\":\"recompute\",\"kv_connector_extra_config\":{\"spark_cache_root\":\"/cache/jit/sparkcache-context/vllm-py-0b67266-native-da4d7be-b12x-b1d541f-tail-cow\",\"spark_cache_model_profile\":\"glm53-flash-hybrid\",\"spark_cache_publication_schema\":\"tail-cow-v1\",\"spark_cache_target_checkpoint_sha256\":\"a35e6bf2875c1875609b8deaec404c07c6cc80259e4222fc0b51e649498bd6b9\",\"spark_cache_draft_checkpoint_sha256\":\"2e06d909ce5bb71c0c0e3e8be74a70e3b41d92ba4c30196cfb0957fb812acef6\",\"spark_cache_draft_policy\":\"separate\",\"spark_cache_store\":true,\"spark_cache_restore\":true,\"spark_cache_scheduler_probe\":\"none\",\"spark_cache_streaming_snapshots\":false,\"spark_cache_cuda_restore\":true,\"spark_cache_max_bytes\":51539607552,\"spark_cache_low_watermark_bytes\":42949672960,\"spark_cache_ttl_seconds\":0,\"spark_cache_min_span_tokens\":4096,\"spark_cache_max_span_tokens\":524288,\"spark_cache_cuda_placement_library\":\"/opt/sparkcache-src/sparkcache/native/build-cuda/libspark_cache_placement.so\",\"spark_cache_cuda_placement_library_sha256\":\"REPLACE_WITH_CUDA_PLACEMENT_LIBRARY_SHA256\",\"spark_cache_cuda_placement_arena_bytes\":268435456,\"spark_cache_cuda_restore_io_workers\":8,\"spark_cache_load_threads\":2,\"spark_cache_clear_once\":\"sparkring-vllm-python-0b67266-native-da4d7be-b12x-b1d541f-tail-cow-initialization\"}}" ], "extra_volumes": [ { @@ -143,7 +143,7 @@ "org.opencontainers.image.base.name": "ghcr.io/fujitsupolycom/sparkring-glm53-runtime@sha256:864adfe68f458223e186a19844ac80c7adc7365e5db1f25e109b85fc19850dcd", "org.sparkring.base.image-id": "sha256:7e8c0ebcb2001efb4cdab0ec9d20d53972e62db3688230044e22e61ffb1d35d5", "org.sparkcache.deployment-profile": "glm53-flash-adaptive-mtp-python-overlay", - "org.sparkcache.native-library-sha256": "REPLACE_WITH_NATIVE_LIBRARY_SHA256", + "org.sparkcache.cuda-placement-library-sha256": "REPLACE_WITH_CUDA_PLACEMENT_LIBRARY_SHA256", "org.sparkcache.source-revision": "5d571018de5b63a9a90e5c11e6d6e86bbff4a957", "org.sparkcache.source-tree": "e864ed9ad64f771188fdb59aa9738e348134d636", "org.sparkcache.source-sha256": "f7c0565521fddeff7085e4cc08043cb8d1e2bde33abc67f83b8608a162d05b88", @@ -156,6 +156,6 @@ "attestation_hook": [ "/bin/sh", "-ec", - "test -f /models/target/config.json && test -f /models/target/model.safetensors.index.json && test \"$(sha256sum /models/target/config.json | cut -d ' ' -f1)\" = 676382abd1e90a6c85f0c8f33d45441ecd45fd514fd7b63ce5610e732d8e4996 && test \"$(sha256sum /models/target/model.safetensors.index.json | cut -d ' ' -f1)\" = 0d1d9e6b226e76520e182de10d4e7194cc885c5cb1bf885bb90de1916ce312cb && test \"$(sha256sum /opt/sparkring/nccl/libnccl.so.2 | cut -d ' ' -f1)\" = 5f1c3f10d5ace66d4ba584415bbfe42b6ac1a0a9116a3b81dcbe50516ad924b3 && grep -q '\"tail-cow-v1\"' /opt/sparkcache-src/sparkcache/spark_context_cache_config.py && test \"$(sha256sum /opt/sparkring/runtime/python-overlay/vllm-python-overlay.json | cut -d ' ' -f1)\" = e5e528288b173399611a4930fecc4182b7208bc1564881d52ca5d2c5c4ae0f6a && test \"$(sha256sum /opt/sparkring/runtime/python-overlay/source-receipt.json | cut -d ' ' -f1)\" = REPLACE_WITH_SOURCE_RECEIPT_SHA256 && test \"$(python3 -c 'import json; print(json.load(open(\"/opt/sparkring/runtime/python-overlay/retained-native.json\", encoding=\"utf-8\"))[\"native_elf_manifest_sha256\"])')\" = REPLACE_WITH_VLLM_NATIVE_ELF_MANIFEST_SHA256 && test \"$(python3 -c 'import json; print(json.load(open(\"/opt/sparkring/runtime/python-overlay/retained-native.json\", encoding=\"utf-8\"))[\"native_dispatch_manifest_sha256\"])')\" = REPLACE_WITH_VLLM_NATIVE_DISPATCH_MANIFEST_SHA256 && test \"$(cat /opt/sparkring/runtime/python-overlay/sparkcache-source-tree.sha256)\" = f7c0565521fddeff7085e4cc08043cb8d1e2bde33abc67f83b8608a162d05b88 && test \"$(sha256sum /opt/sparkcache-src/sparkcache/runtime_patches/vllm-kv-block-lease-contract-glm53-b12x-kda-adaptive-mtp.json | cut -d ' ' -f1)\" = 6defde9551cbb586fd09bb2d3020495531b6573397875a767eaae1dbad126024 && test \"$(sha256sum /opt/sparkcache-src/sparkcache/native/build-cuda/libspark_cache_placement.so | cut -d ' ' -f1)\" = REPLACE_WITH_NATIVE_LIBRARY_SHA256 && python3 /opt/sparkcache-src/sparkcache/runtime_patches/verify_lease_contract.py --vllm-root /usr/local/lib/python3.12/dist-packages --contract /opt/sparkcache-src/sparkcache/runtime_patches/vllm-kv-block-lease-contract-glm53-b12x-kda-adaptive-mtp.json" + "test -f /models/target/config.json && test -f /models/target/model.safetensors.index.json && test \"$(sha256sum /models/target/config.json | cut -d ' ' -f1)\" = 676382abd1e90a6c85f0c8f33d45441ecd45fd514fd7b63ce5610e732d8e4996 && test \"$(sha256sum /models/target/model.safetensors.index.json | cut -d ' ' -f1)\" = 0d1d9e6b226e76520e182de10d4e7194cc885c5cb1bf885bb90de1916ce312cb && test \"$(sha256sum /opt/sparkring/nccl/libnccl.so.2 | cut -d ' ' -f1)\" = 5f1c3f10d5ace66d4ba584415bbfe42b6ac1a0a9116a3b81dcbe50516ad924b3 && grep -q '\"tail-cow-v1\"' /opt/sparkcache-src/sparkcache/spark_context_cache_config.py && test \"$(sha256sum /opt/sparkring/runtime/python-overlay/vllm-python-overlay.json | cut -d ' ' -f1)\" = e5e528288b173399611a4930fecc4182b7208bc1564881d52ca5d2c5c4ae0f6a && test \"$(sha256sum /opt/sparkring/runtime/python-overlay/source-receipt.json | cut -d ' ' -f1)\" = REPLACE_WITH_SOURCE_RECEIPT_SHA256 && test \"$(python3 -c 'import json; print(json.load(open(\"/opt/sparkring/runtime/python-overlay/retained-native.json\", encoding=\"utf-8\"))[\"native_elf_manifest_sha256\"])')\" = REPLACE_WITH_VLLM_NATIVE_ELF_MANIFEST_SHA256 && test \"$(python3 -c 'import json; print(json.load(open(\"/opt/sparkring/runtime/python-overlay/retained-native.json\", encoding=\"utf-8\"))[\"native_dispatch_manifest_sha256\"])')\" = REPLACE_WITH_VLLM_NATIVE_DISPATCH_MANIFEST_SHA256 && test \"$(cat /opt/sparkring/runtime/python-overlay/sparkcache-source-tree.sha256)\" = f7c0565521fddeff7085e4cc08043cb8d1e2bde33abc67f83b8608a162d05b88 && test \"$(sha256sum /opt/sparkcache-src/sparkcache/runtime_patches/vllm-kv-block-lease-contract-glm53-b12x-kda-adaptive-mtp.json | cut -d ' ' -f1)\" = 6defde9551cbb586fd09bb2d3020495531b6573397875a767eaae1dbad126024 && test \"$(sha256sum /opt/sparkcache-src/sparkcache/native/build-cuda/libspark_cache_placement.so | cut -d ' ' -f1)\" = REPLACE_WITH_CUDA_PLACEMENT_LIBRARY_SHA256 && python3 /opt/sparkcache-src/sparkcache/runtime_patches/verify_lease_contract.py --vllm-root /usr/local/lib/python3.12/dist-packages --contract /opt/sparkcache-src/sparkcache/runtime_patches/vllm-kv-block-lease-contract-glm53-b12x-kda-adaptive-mtp.json" ] } diff --git a/scripts/prepare_glm53_b12x_kda_adaptive_mtp_profile.py b/scripts/prepare_glm53_b12x_kda_adaptive_mtp_profile.py index d42972bf..b9aa99a8 100644 --- a/scripts/prepare_glm53_b12x_kda_adaptive_mtp_profile.py +++ b/scripts/prepare_glm53_b12x_kda_adaptive_mtp_profile.py @@ -11,10 +11,17 @@ import yaml +from sparkcache_terminology import ( + SparkCacheTerminologyError, + canonicalize_profile_connector_arguments, + resolve_string_alias, +) + SHA256 = re.compile(r"[0-9a-f]{64}\Z") SHA256_ID = re.compile(r"sha256:[0-9a-f]{64}\Z") -NATIVE_PLACEHOLDER = "REPLACE_WITH_NATIVE_LIBRARY_SHA256" +CUDA_PLACEMENT_PLACEHOLDER = "REPLACE_WITH_CUDA_PLACEMENT_LIBRARY_SHA256" +LEGACY_PLACEMENT_PLACEHOLDER = "REPLACE_WITH_NATIVE_LIBRARY_SHA256" IMAGE_PLACEHOLDER = "REPLACE_WITH_B12X_KDA_ADAPTIVE_MTP_SPARKCACHE_IMAGE" PARENT_PLACEHOLDER = "REPLACE_WITH_B12X_KDA_ADAPTIVE_MTP_RUNTIME_IMAGE" SPARKCACHE_COMMIT = "5d571018de5b63a9a90e5c11e6d6e86bbff4a957" @@ -35,13 +42,18 @@ class ResolveError(ValueError): """A source-built image identity or profile template is incomplete.""" -def _replace_native(value: Any, digest: str) -> Any: +def _replace_cuda_placement(value: Any, digest: str) -> Any: if isinstance(value, str): - return value.replace(NATIVE_PLACEHOLDER, digest) + return value.replace(CUDA_PLACEMENT_PLACEHOLDER, digest).replace( + LEGACY_PLACEMENT_PLACEHOLDER, digest + ) if isinstance(value, list): - return [_replace_native(item, digest) for item in value] + return [_replace_cuda_placement(item, digest) for item in value] if isinstance(value, dict): - return {key: _replace_native(item, digest) for key, item in value.items()} + return { + key: _replace_cuda_placement(item, digest) + for key, item in value.items() + } return value @@ -60,8 +72,19 @@ def resolve( image_id: str, parent_image: str, parent_image_id: str, - native_library_sha256: str, + cuda_placement_library_sha256: str | None = None, + native_library_sha256: str | None = None, ) -> tuple[dict[str, Any], dict[str, Any]]: + try: + profile = canonicalize_profile_connector_arguments(profile) + cuda_placement_library_sha256 = resolve_string_alias( + cuda_placement_library_sha256, + native_library_sha256, + canonical_name="cuda_placement_library_sha256", + legacy_name="native_library_sha256", + ) + except SparkCacheTerminologyError as error: + raise ResolveError(str(error)) from error if not image or IMAGE_PLACEHOLDER in image: raise ResolveError("SparkCache image reference is unresolved") if not parent_image or PARENT_PLACEHOLDER in parent_image: @@ -70,8 +93,10 @@ def resolve( raise ResolveError("SparkCache image ID must be sha256 plus 64 lowercase hex") if SHA256_ID.fullmatch(parent_image_id) is None: raise ResolveError("runtime parent image ID must be sha256 plus 64 lowercase hex") - if SHA256.fullmatch(native_library_sha256) is None: - raise ResolveError("native library SHA-256 must be 64 lowercase hex") + if SHA256.fullmatch(cuda_placement_library_sha256) is None: + raise ResolveError( + "SparkCache CUDA placement library SHA-256 must be 64 lowercase hex" + ) identity = profile.get("identity", {}) if identity.get("sparkcache_source_revision") != SPARKCACHE_COMMIT: raise ResolveError("profile does not name the integrated SparkCache commit") @@ -113,7 +138,7 @@ def resolve( if LEASE_CONTRACT_SHA256 not in attestation: raise ResolveError("profile does not attest the live-tensor KDA lease contract") - profile = _replace_native(profile, native_library_sha256) + profile = _replace_cuda_placement(profile, cuda_placement_library_sha256) profile["image"] = image profile["image_id"] = image_id labels = profile["required_image_labels"] @@ -136,7 +161,8 @@ def main() -> int: parser.add_argument("--image-id", required=True) parser.add_argument("--parent-image", required=True) parser.add_argument("--parent-image-id", required=True) - parser.add_argument("--native-library-sha256", required=True) + parser.add_argument("--cuda-placement-library-sha256") + parser.add_argument("--native-library-sha256") parser.add_argument("--profile-output", type=Path, required=True) parser.add_argument("--site-output", type=Path, required=True) args = parser.parse_args() @@ -150,6 +176,9 @@ def main() -> int: image_id=args.image_id, parent_image=args.parent_image, parent_image_id=args.parent_image_id, + cuda_placement_library_sha256=( + args.cuda_placement_library_sha256 + ), native_library_sha256=args.native_library_sha256, ) except (OSError, KeyError, json.JSONDecodeError, ResolveError) as exc: diff --git a/scripts/prepare_glm53_e105_profile.py b/scripts/prepare_glm53_e105_profile.py index b5cb94af..d8efb654 100644 --- a/scripts/prepare_glm53_e105_profile.py +++ b/scripts/prepare_glm53_e105_profile.py @@ -11,10 +11,17 @@ import yaml +from sparkcache_terminology import ( + SparkCacheTerminologyError, + canonicalize_profile_connector_arguments, + resolve_string_alias, +) + SHA256 = re.compile(r"[0-9a-f]{64}\Z") SHA256_ID = re.compile(r"sha256:[0-9a-f]{64}\Z") -NATIVE_PLACEHOLDER = "REPLACE_WITH_NATIVE_LIBRARY_SHA256" +CUDA_PLACEMENT_PLACEHOLDER = "REPLACE_WITH_CUDA_PLACEMENT_LIBRARY_SHA256" +LEGACY_PLACEMENT_PLACEHOLDER = "REPLACE_WITH_NATIVE_LIBRARY_SHA256" IMAGE_PLACEHOLDER = "REPLACE_WITH_E10536A_SPARKCACHE_IMAGE" PARENT_PLACEHOLDER = "REPLACE_WITH_E10536A_RUNTIME_IMAGE" SPARKCACHE_COMMIT = "eb3690c1aac2b9e86be8d513799dbb64afa53f25" @@ -30,13 +37,18 @@ class ResolveError(ValueError): """A source-built image identity or profile template is incomplete.""" -def _replace_native(value: Any, digest: str) -> Any: +def _replace_cuda_placement(value: Any, digest: str) -> Any: if isinstance(value, str): - return value.replace(NATIVE_PLACEHOLDER, digest) + return value.replace(CUDA_PLACEMENT_PLACEHOLDER, digest).replace( + LEGACY_PLACEMENT_PLACEHOLDER, digest + ) if isinstance(value, list): - return [_replace_native(item, digest) for item in value] + return [_replace_cuda_placement(item, digest) for item in value] if isinstance(value, dict): - return {key: _replace_native(item, digest) for key, item in value.items()} + return { + key: _replace_cuda_placement(item, digest) + for key, item in value.items() + } return value @@ -48,8 +60,19 @@ def resolve( image_id: str, parent_image: str, parent_image_id: str, - native_library_sha256: str, + cuda_placement_library_sha256: str | None = None, + native_library_sha256: str | None = None, ) -> tuple[dict[str, Any], dict[str, Any]]: + try: + profile = canonicalize_profile_connector_arguments(profile) + cuda_placement_library_sha256 = resolve_string_alias( + cuda_placement_library_sha256, + native_library_sha256, + canonical_name="cuda_placement_library_sha256", + legacy_name="native_library_sha256", + ) + except SparkCacheTerminologyError as error: + raise ResolveError(str(error)) from error if not image or IMAGE_PLACEHOLDER in image: raise ResolveError("SparkCache image reference is unresolved") if not parent_image or PARENT_PLACEHOLDER in parent_image: @@ -58,8 +81,10 @@ def resolve( raise ResolveError("SparkCache image ID must be sha256 plus 64 lowercase hex") if SHA256_ID.fullmatch(parent_image_id) is None: raise ResolveError("runtime parent image ID must be sha256 plus 64 lowercase hex") - if SHA256.fullmatch(native_library_sha256) is None: - raise ResolveError("native library SHA-256 must be 64 lowercase hex") + if SHA256.fullmatch(cuda_placement_library_sha256) is None: + raise ResolveError( + "SparkCache CUDA placement library SHA-256 must be 64 lowercase hex" + ) identity = profile.get("identity", {}) if identity.get("sparkcache_source_revision") != SPARKCACHE_COMMIT: raise ResolveError("profile does not name the integrated SparkCache commit") @@ -71,7 +96,7 @@ def resolve( if LEASE_CONTRACT_SHA256 not in attestation: raise ResolveError("profile does not attest the e10536a lease contract") - profile = _replace_native(profile, native_library_sha256) + profile = _replace_cuda_placement(profile, cuda_placement_library_sha256) profile["image"] = image profile["image_id"] = image_id labels = profile["required_image_labels"] @@ -94,7 +119,8 @@ def main() -> int: parser.add_argument("--image-id", required=True) parser.add_argument("--parent-image", required=True) parser.add_argument("--parent-image-id", required=True) - parser.add_argument("--native-library-sha256", required=True) + parser.add_argument("--cuda-placement-library-sha256") + parser.add_argument("--native-library-sha256") parser.add_argument("--profile-output", type=Path, required=True) parser.add_argument("--site-output", type=Path, required=True) args = parser.parse_args() @@ -108,6 +134,9 @@ def main() -> int: image_id=args.image_id, parent_image=args.parent_image, parent_image_id=args.parent_image_id, + cuda_placement_library_sha256=( + args.cuda_placement_library_sha256 + ), native_library_sha256=args.native_library_sha256, ) except (OSError, KeyError, json.JSONDecodeError, ResolveError) as exc: diff --git a/scripts/prepare_glm53_public_python_overlay_profile.py b/scripts/prepare_glm53_public_python_overlay_profile.py index 40ec4049..95bcf35f 100644 --- a/scripts/prepare_glm53_public_python_overlay_profile.py +++ b/scripts/prepare_glm53_public_python_overlay_profile.py @@ -12,12 +12,22 @@ import yaml +from sparkcache_terminology import ( + SparkCacheTerminologyError, + canonicalize_profile_connector_arguments, + resolve_string_alias, +) + SHA256 = re.compile(r"[0-9a-f]{64}\Z") SHA256_ID = re.compile(r"sha256:[0-9a-f]{64}\Z") IMAGE_PLACEHOLDER = "REPLACE_WITH_PUBLIC_PYTHON_OVERLAY_SPARKCACHE_IMAGE" +CUDA_PLACEMENT_PLACEHOLDER = "REPLACE_WITH_CUDA_PLACEMENT_LIBRARY_SHA256" +LEGACY_PLACEMENT_PLACEHOLDER = "REPLACE_WITH_NATIVE_LIBRARY_SHA256" +CUDA_PLACEMENT_LABEL = "org.sparkcache.cuda-placement-library-sha256" +LEGACY_PLACEMENT_LABEL = "org.sparkcache.native-library-sha256" PLACEHOLDERS = { - "REPLACE_WITH_NATIVE_LIBRARY_SHA256": "native_library_sha256", + CUDA_PLACEMENT_PLACEHOLDER: "cuda_placement_library_sha256", "REPLACE_WITH_VLLM_NATIVE_ELF_MANIFEST_SHA256": "native_elf_manifest_sha256", "REPLACE_WITH_VLLM_NATIVE_DISPATCH_MANIFEST_SHA256": ( "native_dispatch_manifest_sha256" @@ -98,17 +108,46 @@ def resolve( *, image: str, image_id: str, - native_library_sha256: str, + cuda_placement_library_sha256: str | None = None, native_elf_manifest_sha256: str, native_dispatch_manifest_sha256: str, source_receipt_sha256: str, + native_library_sha256: str | None = None, ) -> tuple[dict[str, Any], dict[str, Any]]: + try: + profile = canonicalize_profile_connector_arguments(profile) + cuda_placement_library_sha256 = resolve_string_alias( + cuda_placement_library_sha256, + native_library_sha256, + canonical_name="cuda_placement_library_sha256", + legacy_name="native_library_sha256", + ) + except SparkCacheTerminologyError as error: + raise ResolveError(str(error)) from error + profile = _replace( + profile, + {LEGACY_PLACEMENT_PLACEHOLDER: CUDA_PLACEMENT_PLACEHOLDER}, + ) + labels = dict(profile.get("required_image_labels", {})) + if ( + CUDA_PLACEMENT_LABEL in labels + and LEGACY_PLACEMENT_LABEL in labels + and labels[CUDA_PLACEMENT_LABEL] != labels[LEGACY_PLACEMENT_LABEL] + ): + raise ResolveError( + f"profile image labels {CUDA_PLACEMENT_LABEL} and compatibility " + f"alias {LEGACY_PLACEMENT_LABEL} have conflicting values" + ) + if LEGACY_PLACEMENT_LABEL in labels and CUDA_PLACEMENT_LABEL not in labels: + labels[CUDA_PLACEMENT_LABEL] = labels[LEGACY_PLACEMENT_LABEL] + labels.pop(LEGACY_PLACEMENT_LABEL, None) + profile["required_image_labels"] = labels if not image or IMAGE_PLACEHOLDER in image: raise ResolveError("Python-overlay image reference is unresolved") if SHA256_ID.fullmatch(image_id) is None: raise ResolveError("Python-overlay image ID must be sha256 plus 64 lowercase hex") supplied = { - "native_library_sha256": native_library_sha256, + "cuda_placement_library_sha256": cuda_placement_library_sha256, "native_elf_manifest_sha256": native_elf_manifest_sha256, "native_dispatch_manifest_sha256": native_dispatch_manifest_sha256, "source_receipt_sha256": source_receipt_sha256, @@ -234,7 +273,8 @@ def main() -> int: parser.add_argument("--site-template", type=Path, required=True) parser.add_argument("--image", required=True) parser.add_argument("--image-id", required=True) - parser.add_argument("--native-library-sha256", required=True) + parser.add_argument("--cuda-placement-library-sha256") + parser.add_argument("--native-library-sha256") parser.add_argument("--native-elf-manifest-sha256", required=True) parser.add_argument("--native-dispatch-manifest-sha256", required=True) parser.add_argument("--source-receipt-sha256", required=True) @@ -249,10 +289,13 @@ def main() -> int: site, image=args.image, image_id=args.image_id, - native_library_sha256=args.native_library_sha256, + cuda_placement_library_sha256=( + args.cuda_placement_library_sha256 + ), native_elf_manifest_sha256=args.native_elf_manifest_sha256, native_dispatch_manifest_sha256=args.native_dispatch_manifest_sha256, source_receipt_sha256=args.source_receipt_sha256, + native_library_sha256=args.native_library_sha256, ) except (OSError, KeyError, json.JSONDecodeError, ResolveError) as exc: parser.error(str(exc)) diff --git a/scripts/sparkcache_terminology.py b/scripts/sparkcache_terminology.py new file mode 100644 index 00000000..ff7427b3 --- /dev/null +++ b/scripts/sparkcache_terminology.py @@ -0,0 +1,151 @@ +"""Canonical SparkCache CUDA configuration names and compatibility aliases.""" + +from __future__ import annotations + +import json +from collections.abc import Mapping, Sequence +from typing import Any + + +CANONICAL_CONNECTOR_KEYS = { + "spark_cache_cuda_restore": "spark_cache_native_restore", + "spark_cache_cuda_placement_library": "spark_cache_native_library", + "spark_cache_cuda_placement_library_sha256": ( + "spark_cache_native_library_sha256" + ), + "spark_cache_cuda_placement_arena_bytes": "spark_cache_native_arena_bytes", + "spark_cache_cuda_restore_io_workers": "spark_cache_native_io_workers", +} + + +class SparkCacheTerminologyError(ValueError): + """Canonical and compatibility names cannot produce one exact contract.""" + + +def _same_json_value(left: Any, right: Any) -> bool: + return type(left) is type(right) and left == right + + +def canonicalize_connector_extra_config(extra: Mapping[str, Any]) -> dict[str, Any]: + """Return canonical connector keys and reject contradictory aliases.""" + + result = dict(extra) + for canonical, legacy in CANONICAL_CONNECTOR_KEYS.items(): + canonical_present = canonical in result + legacy_present = legacy in result + if canonical_present and legacy_present and not _same_json_value( + result[canonical], result[legacy] + ): + raise SparkCacheTerminologyError( + f"SparkCache connector keys {canonical} and compatibility alias " + f"{legacy} have conflicting values" + ) + if legacy_present and not canonical_present: + result[canonical] = result[legacy] + result.pop(legacy, None) + return result + + +def canonicalize_connector_arguments(arguments: Sequence[str]) -> tuple[str, ...]: + """Normalize SparkCache keys inside one vLLM KV-transfer argument.""" + + result = tuple(arguments) + locations: list[tuple[int, str, str]] = [] + for index, argument in enumerate(result): + if argument == "--kv-transfer-config": + if index + 1 >= len(result): + raise SparkCacheTerminologyError( + "--kv-transfer-config requires a JSON value" + ) + locations.append((index + 1, result[index + 1], "separate")) + elif argument.startswith("--kv-transfer-config="): + locations.append( + ( + index, + argument.split("=", 1)[1], + "equals", + ) + ) + if not locations: + return result + if len(locations) != 1: + raise SparkCacheTerminologyError( + "a runtime profile must contain at most one --kv-transfer-config" + ) + + index, encoded, form = locations[0] + try: + transfer = json.loads(encoded) + except json.JSONDecodeError as error: + raise SparkCacheTerminologyError( + "--kv-transfer-config must contain valid JSON" + ) from error + if not isinstance(transfer, dict): + raise SparkCacheTerminologyError( + "--kv-transfer-config must contain a JSON object" + ) + extra = transfer.get("kv_connector_extra_config") + if extra is None: + return result + if not isinstance(extra, dict): + raise SparkCacheTerminologyError( + "kv_connector_extra_config must contain a JSON object" + ) + canonical = canonicalize_connector_extra_config(extra) + if canonical == extra: + return result + + transfer["kv_connector_extra_config"] = canonical + replacement = json.dumps(transfer, separators=(",", ":")) + mutable = list(result) + mutable[index] = ( + replacement + if form == "separate" + else f"--kv-transfer-config={replacement}" + ) + return tuple(mutable) + + +def canonicalize_profile_connector_arguments( + profile: Mapping[str, Any], +) -> dict[str, Any]: + """Return a profile whose emitted vLLM arguments use canonical keys.""" + + result = dict(profile) + arguments = result.get("extra_vllm_args") + if arguments is None: + return result + if not isinstance(arguments, list) or not all( + isinstance(argument, str) for argument in arguments + ): + raise SparkCacheTerminologyError( + "profile extra_vllm_args must contain a JSON string array" + ) + normalized = canonicalize_connector_arguments(arguments) + if normalized != tuple(arguments): + result["extra_vllm_args"] = list(normalized) + return result + + +def resolve_string_alias( + canonical_value: str | None, + legacy_value: str | None, + *, + canonical_name: str, + legacy_name: str, +) -> str: + """Resolve one CLI/Python alias pair without accepting ambiguity.""" + + if ( + canonical_value is not None + and legacy_value is not None + and canonical_value != legacy_value + ): + raise SparkCacheTerminologyError( + f"{canonical_name} and compatibility alias {legacy_name} " + "have conflicting values" + ) + selected = canonical_value if canonical_value is not None else legacy_value + if selected is None: + raise SparkCacheTerminologyError(f"{canonical_name} is required") + return selected diff --git a/scripts/sparkring_runtime.py b/scripts/sparkring_runtime.py index 3a854999..21bb865d 100644 --- a/scripts/sparkring_runtime.py +++ b/scripts/sparkring_runtime.py @@ -34,6 +34,11 @@ from pathlib import Path from typing import Any, Mapping +from sparkcache_terminology import ( + SparkCacheTerminologyError, + canonicalize_connector_arguments, +) + # --------------------------------------------------------------------------- # Schema constants @@ -514,6 +519,10 @@ def parse_runtime_profile( extra_vllm_args = _validate_extra_vllm_args( document["extra_vllm_args"], f"{where}.extra_vllm_args", ) + try: + extra_vllm_args = canonicalize_connector_arguments(extra_vllm_args) + except SparkCacheTerminologyError as error: + raise ProfileError(f"{where}.extra_vllm_args: {error}") from error extra_volumes = _validate_extra_volumes( document.get("extra_volumes"), f"{where}.extra_volumes", ) diff --git a/scripts/test_glm53_flash_profile.py b/scripts/test_glm53_flash_profile.py index ea6faec4..c4edb2be 100644 --- a/scripts/test_glm53_flash_profile.py +++ b/scripts/test_glm53_flash_profile.py @@ -253,7 +253,8 @@ def test_cache_profile_adds_only_the_external_connector_to_serving_arguments() - assert extra["spark_cache_model_profile"] == "glm53-flash-hybrid" assert extra["spark_cache_draft_policy"] == "separate" assert extra["spark_cache_streaming_snapshots"] is False - assert extra["spark_cache_native_restore"] is False + assert extra["spark_cache_cuda_restore"] is False + assert not any(key.startswith("spark_cache_native_") for key in extra) for attribute in ( "image", diff --git a/scripts/test_prepare_glm53_b12x_kda_adaptive_mtp_profile.py b/scripts/test_prepare_glm53_b12x_kda_adaptive_mtp_profile.py index 3e99f953..d1386c27 100644 --- a/scripts/test_prepare_glm53_b12x_kda_adaptive_mtp_profile.py +++ b/scripts/test_prepare_glm53_b12x_kda_adaptive_mtp_profile.py @@ -75,9 +75,10 @@ def test_profile_pins_adaptive_mtp_fastsafetensors_and_sparkcache() -> None: transfer = json.loads(_argument(profile, "--kv-transfer-config")) extra = transfer["kv_connector_extra_config"] assert extra["spark_cache_draft_checkpoint_sha256"] == MTP_CACHE_IDENTITY_SHA256 - assert extra["spark_cache_native_restore"] is True - assert extra["spark_cache_native_arena_bytes"] == 256 * 1024**2 - assert extra["spark_cache_native_io_workers"] == 8 + assert extra["spark_cache_cuda_restore"] is True + assert extra["spark_cache_cuda_placement_arena_bytes"] == 256 * 1024**2 + assert extra["spark_cache_cuda_restore_io_workers"] == 8 + assert not any(key.startswith("spark_cache_native_") for key in extra) assert extra["spark_cache_load_threads"] == 2 assert extra["spark_cache_clear_once"] == ( "sparkring-b12x-kda-adaptive-mtp-fastsafetensors-initialization" @@ -119,7 +120,7 @@ def test_resolver_produces_an_aligned_tp4_profile(tmp_path: Path) -> None: image_id="sha256:" + "b" * 64, parent_image="local/glm53-kda-runtime@sha256:" + "c" * 64, parent_image_id="sha256:" + "d" * 64, - native_library_sha256="e" * 64, + cuda_placement_library_sha256="e" * 64, ) assert site["topology"] and len(site["ranks"]) == 4 assert site["serving"]["tensor_parallel_size"] == 4 @@ -144,7 +145,7 @@ def test_resolver_rejects_runtime_or_loader_identity_drift() -> None: "image_id": "sha256:" + "a" * 64, "parent_image": "parent", "parent_image_id": "sha256:" + "b" * 64, - "native_library_sha256": "c" * 64, + "cuda_placement_library_sha256": "c" * 64, } changed = copy.deepcopy(profile) changed["identity"]["vllm_revision"] = "0" * 40 @@ -162,6 +163,69 @@ def test_resolver_rejects_runtime_or_loader_identity_drift() -> None: resolve(changed, copy.deepcopy(site), **arguments) +def test_resolver_normalizes_legacy_cuda_restore_aliases_and_rejects_conflicts() -> None: + profile = json.loads(PROFILE.read_text(encoding="utf-8")) + site = yaml.safe_load(SITE.read_text(encoding="utf-8")) + arguments = profile["extra_vllm_args"] + index = arguments.index("--kv-transfer-config") + 1 + transfer = json.loads(arguments[index]) + extra = transfer["kv_connector_extra_config"] + extra["spark_cache_native_restore"] = extra.pop("spark_cache_cuda_restore") + extra["spark_cache_native_library"] = extra.pop( + "spark_cache_cuda_placement_library" + ) + arguments[index] = json.dumps(transfer, separators=(",", ":")) + + resolved, _ = resolve( + profile, + site, + image="image", + image_id="sha256:" + "a" * 64, + parent_image="parent", + parent_image_id="sha256:" + "b" * 64, + native_library_sha256="c" * 64, + ) + resolved_extra = json.loads(_argument(resolved, "--kv-transfer-config"))[ + "kv_connector_extra_config" + ] + assert resolved_extra["spark_cache_cuda_restore"] is True + assert "spark_cache_native_restore" not in resolved_extra + assert "spark_cache_native_library" not in resolved_extra + + conflict = copy.deepcopy(profile) + conflict_arguments = conflict["extra_vllm_args"] + conflict_index = conflict_arguments.index("--kv-transfer-config") + 1 + conflict_transfer = json.loads(conflict_arguments[conflict_index]) + conflict_transfer["kv_connector_extra_config"][ + "spark_cache_cuda_restore" + ] = False + conflict_arguments[conflict_index] = json.dumps( + conflict_transfer, separators=(",", ":") + ) + with pytest.raises(ResolveError, match="conflicting values"): + resolve( + conflict, + copy.deepcopy(site), + image="image", + image_id="sha256:" + "a" * 64, + parent_image="parent", + parent_image_id="sha256:" + "b" * 64, + cuda_placement_library_sha256="c" * 64, + ) + + with pytest.raises(ResolveError, match="conflicting values"): + resolve( + json.loads(PROFILE.read_text(encoding="utf-8")), + copy.deepcopy(site), + image="image", + image_id="sha256:" + "a" * 64, + parent_image="parent", + parent_image_id="sha256:" + "b" * 64, + cuda_placement_library_sha256="c" * 64, + native_library_sha256="d" * 64, + ) + + def test_quickstart_names_the_executable_builder_and_profile_contracts() -> None: guide = QUICKSTART.read_text(encoding="utf-8") assert "runtime/glm53-flash-b12x-kda-adaptive-mtp/build-image.sh" in guide @@ -173,3 +237,5 @@ def test_quickstart_names_the_executable_builder_and_profile_contracts() -> None assert SPARKCACHE_SOURCE_SHA256 in guide assert VLLM_COMMIT in guide assert "START_GLM53_FLASH_MTP5_ADAPTIVE_FASTSAFETENSORS_TP4" in guide + assert "--cuda-placement-library-sha256" in guide + assert "--native-library-sha256" not in guide diff --git a/scripts/test_prepare_glm53_e105_profile.py b/scripts/test_prepare_glm53_e105_profile.py index cc9257e2..541e602d 100644 --- a/scripts/test_prepare_glm53_e105_profile.py +++ b/scripts/test_prepare_glm53_e105_profile.py @@ -96,9 +96,10 @@ def test_profile_identities_follow_model_and_speculator_semantics() -> None: profile_ids.add(profile["profile_id"]) container_names.add(profile["container_name"]) confirmations.add(profile["confirmation"]) - assert extra["spark_cache_native_restore"] is True - assert extra["spark_cache_native_arena_bytes"] == 256 * 1024**2 - assert extra["spark_cache_native_io_workers"] == 8 + assert extra["spark_cache_cuda_restore"] is True + assert extra["spark_cache_cuda_placement_arena_bytes"] == 256 * 1024**2 + assert extra["spark_cache_cuda_restore_io_workers"] == 8 + assert not any(key.startswith("spark_cache_native_") for key in extra) assert extra["spark_cache_load_threads"] == 2 assert len(identities) == 3 assert len(clear_tokens) == len(PROFILES) @@ -167,7 +168,7 @@ def test_resolver_produces_aligned_twenty_gib_profile() -> None: image_id="sha256:" + "b" * 64, parent_image="local/e105-runtime@sha256:" + "c" * 64, parent_image_id="sha256:" + "d" * 64, - native_library_sha256="e" * 64, + cuda_placement_library_sha256="e" * 64, ) assert resolved_site["runtime"]["container_image"] == resolved_profile["image"] assert resolved_site["runtime"]["container_image_digest"] == resolved_profile["image_id"] @@ -188,7 +189,7 @@ def test_each_resolved_profile_passes_site_alignment( image_id="sha256:" + "b" * 64, parent_image="local/e105-runtime@sha256:" + "c" * 64, parent_image_id="sha256:" + "d" * 64, - native_library_sha256="e" * 64, + cuda_placement_library_sha256="e" * 64, ) profile_path_out = tmp_path / "profile.json" site_path_out = tmp_path / "site.yaml" @@ -204,12 +205,12 @@ def test_each_resolved_profile_passes_site_alignment( def test_resolver_rejects_unverified_native_library() -> None: profile = json.loads(PROFILES["dflash"].read_text()) site = yaml.safe_load(SITE.read_text()) - with pytest.raises(ResolveError, match="native library"): + with pytest.raises(ResolveError, match="CUDA placement library"): resolve( profile, site, image="image", image_id="sha256:" + "a" * 64, parent_image="parent", parent_image_id="sha256:" + "b" * 64, - native_library_sha256="short", + cuda_placement_library_sha256="short", ) diff --git a/scripts/test_prepare_glm53_public_python_overlay_profile.py b/scripts/test_prepare_glm53_public_python_overlay_profile.py index aad7d3ff..074c201a 100644 --- a/scripts/test_prepare_glm53_public_python_overlay_profile.py +++ b/scripts/test_prepare_glm53_public_python_overlay_profile.py @@ -30,7 +30,7 @@ ) SITE = CONFIG / "glm53-flash-b12x-kda-adaptive-mtp-tp4-site.example.yaml" IMAGE_ID = "sha256:" + "ab" * 32 -NATIVE_LIBRARY = "1a" * 32 +CUDA_PLACEMENT_LIBRARY = "1a" * 32 NATIVE_ELF = "2b" * 32 NATIVE_DISPATCH = "3c" * 32 SOURCE_RECEIPT = "4d" * 32 @@ -70,7 +70,7 @@ def _resolved() -> tuple[dict, dict]: site, image="local/glm53-public-python-overlay@sha256:" + "a" * 64, image_id=IMAGE_ID, - native_library_sha256=NATIVE_LIBRARY, + cuda_placement_library_sha256=CUDA_PLACEMENT_LIBRARY, native_elf_manifest_sha256=NATIVE_ELF, native_dispatch_manifest_sha256=NATIVE_DISPATCH, source_receipt_sha256=SOURCE_RECEIPT, @@ -98,6 +98,10 @@ def test_profile_selects_opaque_page_tail_copy_on_write() -> None: profile = json.loads(PROFILE.read_text(encoding="utf-8")) transfer = json.loads(_argument(profile, "--kv-transfer-config")) extra = transfer["kv_connector_extra_config"] + assert extra["spark_cache_cuda_restore"] is True + assert extra["spark_cache_cuda_placement_arena_bytes"] == 256 * 1024**2 + assert extra["spark_cache_cuda_restore_io_workers"] == 8 + assert not any(key.startswith("spark_cache_native_") for key in extra) assert extra["spark_cache_publication_schema"] == "tail-cow-v1" assert "tail-cow" in extra["spark_cache_root"] assert "tail-cow" in extra["spark_cache_clear_once"] @@ -130,7 +134,10 @@ def test_resolver_requires_mixed_provenance_and_all_artifact_hashes() -> None: assert labels["org.sparkring.vllm.native-dispatch-manifest-sha256"] == ( NATIVE_DISPATCH ) - assert labels["org.sparkcache.native-library-sha256"] == NATIVE_LIBRARY + assert labels["org.sparkcache.cuda-placement-library-sha256"] == ( + CUDA_PLACEMENT_LIBRARY + ) + assert "org.sparkcache.native-library-sha256" not in labels assert labels["org.sparkcache.source-tree"] == ( "e864ed9ad64f771188fdb59aa9738e348134d636" ) @@ -147,7 +154,7 @@ def test_resolver_rejects_snapshot_publication_or_source_built_labels() -> None: arguments = { "image": "image", "image_id": "sha256:" + "a" * 64, - "native_library_sha256": "b" * 64, + "cuda_placement_library_sha256": "b" * 64, "native_elf_manifest_sha256": "c" * 64, "native_dispatch_manifest_sha256": "d" * 64, "source_receipt_sha256": "e" * 64, @@ -174,6 +181,52 @@ def test_resolver_rejects_snapshot_publication_or_source_built_labels() -> None: resolve(changed, copy.deepcopy(site), **arguments) +def test_resolver_normalizes_legacy_placement_label_and_digest_alias() -> None: + profile = json.loads(PROFILE.read_text(encoding="utf-8")) + site = yaml.safe_load(SITE.read_text(encoding="utf-8")) + labels = profile["required_image_labels"] + labels["org.sparkcache.native-library-sha256"] = labels.pop( + "org.sparkcache.cuda-placement-library-sha256" + ) + + resolved, _ = resolve( + profile, + site, + image="image", + image_id="sha256:" + "a" * 64, + native_library_sha256="b" * 64, + native_elf_manifest_sha256="c" * 64, + native_dispatch_manifest_sha256="d" * 64, + source_receipt_sha256="e" * 64, + ) + + resolved_labels = resolved["required_image_labels"] + assert resolved_labels["org.sparkcache.cuda-placement-library-sha256"] == ( + "b" * 64 + ) + assert "org.sparkcache.native-library-sha256" not in resolved_labels + + +def test_resolver_rejects_conflicting_placement_label_aliases() -> None: + profile = json.loads(PROFILE.read_text(encoding="utf-8")) + site = yaml.safe_load(SITE.read_text(encoding="utf-8")) + profile["required_image_labels"][ + "org.sparkcache.native-library-sha256" + ] = "0" * 64 + + with pytest.raises(ResolveError, match="conflicting values"): + resolve( + profile, + site, + image="image", + image_id="sha256:" + "a" * 64, + cuda_placement_library_sha256="b" * 64, + native_elf_manifest_sha256="c" * 64, + native_dispatch_manifest_sha256="d" * 64, + source_receipt_sha256="e" * 64, + ) + + def test_generic_launcher_builds_a_four_rank_dry_run( tmp_path: Path, capsys: pytest.CaptureFixture[str] ) -> None: diff --git a/scripts/test_sparkcache_recipes.py b/scripts/test_sparkcache_recipes.py index d9a12686..feaff054 100644 --- a/scripts/test_sparkcache_recipes.py +++ b/scripts/test_sparkcache_recipes.py @@ -75,7 +75,8 @@ def test_compositions_pin_artifact_and_fail_closed_policy() -> None: assert recipe["serving"]["scheduler_budget_status"] == "qualified" assert recipe["sparkcache"]["kv_load_failure_policy"] == "recompute" assert recipe["sparkcache"]["streaming_snapshots"] is False - assert recipe["sparkcache"]["native_restore"] is False + assert recipe["sparkcache"]["cuda_restore"] is False + assert "native_restore" not in recipe["sparkcache"] def test_scheduler_budget_records_evidence_without_an_operator_ceiling() -> None: diff --git a/scripts/test_sparkcache_terminology.py b/scripts/test_sparkcache_terminology.py new file mode 100644 index 00000000..3b3e3476 --- /dev/null +++ b/scripts/test_sparkcache_terminology.py @@ -0,0 +1,146 @@ +"""GPU-free contracts for SparkCache CUDA configuration terminology.""" + +from __future__ import annotations + +import json +from pathlib import Path + +import pytest + +from sparkcache_terminology import ( + CANONICAL_CONNECTOR_KEYS, + SparkCacheTerminologyError, + canonicalize_connector_arguments, + canonicalize_connector_extra_config, + resolve_string_alias, +) + + +ROOT = Path(__file__).resolve().parents[1] + + +def test_every_connector_alias_normalizes_to_its_canonical_key() -> None: + legacy = { + old: index + for index, old in enumerate(CANONICAL_CONNECTOR_KEYS.values(), start=1) + } + + normalized = canonicalize_connector_extra_config(legacy) + + assert normalized == { + canonical: index + for index, canonical in enumerate(CANONICAL_CONNECTOR_KEYS, start=1) + } + + +def test_connector_alias_conflict_is_rejected_even_across_json_types() -> None: + with pytest.raises(SparkCacheTerminologyError, match="conflicting values"): + canonicalize_connector_extra_config( + { + "spark_cache_cuda_restore": True, + "spark_cache_native_restore": 1, + } + ) + + +def test_runtime_argument_normalizes_legacy_connector_json() -> None: + transfer = { + "kv_connector": "SparkContextCacheConnector", + "kv_connector_extra_config": { + "spark_cache_native_restore": True, + "spark_cache_native_library": "/opt/lib/libspark_cache_placement.so", + }, + } + + normalized = canonicalize_connector_arguments( + ("--kv-transfer-config", json.dumps(transfer)) + ) + extra = json.loads(normalized[1])["kv_connector_extra_config"] + + assert extra == { + "spark_cache_cuda_restore": True, + "spark_cache_cuda_placement_library": ( + "/opt/lib/libspark_cache_placement.so" + ), + } + + +def test_runtime_argument_rejects_conflicting_connector_json() -> None: + transfer = { + "kv_connector_extra_config": { + "spark_cache_cuda_restore": True, + "spark_cache_native_restore": False, + } + } + with pytest.raises(SparkCacheTerminologyError, match="conflicting values"): + canonicalize_connector_arguments( + ("--kv-transfer-config=" + json.dumps(transfer),) + ) + + +def test_cli_alias_accepts_one_name_or_equal_values_and_rejects_conflicts() -> None: + assert ( + resolve_string_alias( + "a" * 64, + None, + canonical_name="--cuda-placement-library-sha256", + legacy_name="--native-library-sha256", + ) + == "a" * 64 + ) + assert ( + resolve_string_alias( + "b" * 64, + "b" * 64, + canonical_name="--cuda-placement-library-sha256", + legacy_name="--native-library-sha256", + ) + == "b" * 64 + ) + with pytest.raises(SparkCacheTerminologyError, match="conflicting values"): + resolve_string_alias( + "a" * 64, + "b" * 64, + canonical_name="--cuda-placement-library-sha256", + legacy_name="--native-library-sha256", + ) + + +def test_generated_profiles_emit_only_canonical_connector_and_label_names() -> None: + forbidden = ( + "spark_cache_native_restore", + "spark_cache_native_library", + "spark_cache_native_library_sha256", + "spark_cache_native_arena_bytes", + "spark_cache_native_io_workers", + "REPLACE_WITH_NATIVE_LIBRARY_SHA256", + "org.sparkcache.native-library-sha256", + ) + profiles = tuple((ROOT / "scripts/config").glob("*sparkcache*.json")) + assert profiles + for path in profiles: + text = path.read_text(encoding="utf-8") + assert not any(name in text for name in forbidden), path.name + + +def test_operator_prose_uses_sparkcache_cuda_restore_and_placement_names() -> None: + paths = ( + ROOT / "docs/GLM53_B12X_KDA_ADAPTIVE_MTP_SPARKCACHE_TP4_QUICKSTART.md", + ROOT / "docs/GLM53_E10536A_SPARKCACHE_TP4_QUICKSTART.md", + ROOT / "recipes/sparkcache/README.md", + ROOT / "runtime/glm53-flash-adaptive-mtp-python-overlay/README.md", + ROOT / "runtime/glm53-flash-b12x-kda-adaptive-mtp/README.md", + ROOT / "scripts/config/README.md", + ) + forbidden = ( + "sparkcache native", + "native restore", + "native placement", + "native direct restore", + "native page placement", + "--native-library-sha256", + ) + text = "\n".join(path.read_text(encoding="utf-8").lower() for path in paths) + assert "sparkcache cuda restore" in text + assert "sparkcache cuda placement" in text + assert not any(phrase in text for phrase in forbidden) diff --git a/scripts/test_sparkring_generic_launcher.py b/scripts/test_sparkring_generic_launcher.py index 5fc69e44..5d3d829a 100644 --- a/scripts/test_sparkring_generic_launcher.py +++ b/scripts/test_sparkring_generic_launcher.py @@ -59,6 +59,50 @@ def test_native_profile_loads_with_declared_identity(tmp_path): assert profile.identity == {"model_revision": "b" * 40} +def test_profile_loader_normalizes_legacy_sparkcache_connector_keys(tmp_path): + transfer = { + "kv_connector": "SparkContextCacheConnector", + "kv_connector_extra_config": {"spark_cache_native_restore": True}, + } + profile = generic.load_profile( + _write_profile( + tmp_path, + "sparkcache-alias.json", + _native_document( + extra_vllm_args=["--kv-transfer-config", json.dumps(transfer)] + ), + ) + ) + + normalized = json.loads(profile.extra_vllm_args[1]) + assert normalized["kv_connector_extra_config"] == { + "spark_cache_cuda_restore": True + } + + +def test_profile_loader_rejects_conflicting_sparkcache_connector_keys(tmp_path): + transfer = { + "kv_connector": "SparkContextCacheConnector", + "kv_connector_extra_config": { + "spark_cache_cuda_restore": True, + "spark_cache_native_restore": False, + }, + } + with pytest.raises(runtime.ProfileError, match="conflicting values"): + generic.load_profile( + _write_profile( + tmp_path, + "sparkcache-conflict.json", + _native_document( + extra_vllm_args=[ + "--kv-transfer-config", + json.dumps(transfer), + ] + ), + ) + ) + + @pytest.mark.parametrize( "document, error", [