diff --git a/README.md b/README.md index a0423ec..7201fb5 100644 --- a/README.md +++ b/README.md @@ -549,12 +549,14 @@ obliteratus obliterate Qwen/Qwen3.8-27B --dtype bfloat16 obliteratus obliterate Qwen/Qwen3.8-27B --dtype float16 --quantization 4bit ``` -> **Qwen3.8 safety status:** loading the native BF16 checkpoint is supported, but -> permanent abliteration of its Qwen3.5 hybrid Gated DeltaNet/attention layout is -> currently unvalidated. The pipeline checks pristine perplexity and coherence, -> then stops before modifying weights with an unsupported-architecture error. -> Qwen3.8 is not considered surgery-supported until projection-family validation -> and a real post-surgery regression pass are published. +> **Qwen3.8 safety status:** Qwen3.8's hybrid Gated DeltaNet runtime requires the +> supported FLA and causal-conv1d CUDA kernels for correct pristine logits. The +> generic PyTorch fallback and automatic multi-GPU layer sharding are rejected. +> OBLITERATUS places the complete text model on one CUDA device with 15% headroom +> and stops before allocation if that contract cannot be met. Install a +> PyTorch/CUDA-compatible kernel build with `pip install -e ".[qwen-hybrid]"`. +> The pristine and post-edit quality gates remain mandatory; a failed pristine +> checkpoint is never modified. Install the optional backend before selecting a bitsandbytes mode: diff --git a/docs/conditional-testing.md b/docs/conditional-testing.md index 7b3a6b7..2d25a87 100644 --- a/docs/conditional-testing.md +++ b/docs/conditional-testing.md @@ -86,6 +86,27 @@ uv pip check --python .venv/bin/python .venv/bin/python scripts/run_conditional_gate.py bitsandbytes-runtime ``` +### Qwen3.5/Qwen3.8 hybrid runtime + +The Qwen3.5/Qwen3.8 Gated DeltaNet path requires both FLA and causal-conv1d. +Install the optional extra only after selecting the CUDA build of PyTorch: + +```bash +CUDA_HOME=/usr/local/cuda uv sync --locked --extra dev --extra qwen-hybrid +.venv/bin/python -c 'import torch, fla, causal_conv1d; print(torch.__version__)' +``` + +`causal-conv1d` wheels are keyed to the Python, PyTorch, CUDA, platform, and C++ +ABI combination. Prefer an exact upstream wheel when one exists. A source build +must use a `CUDA_HOME` whose major version matches `torch.version.cuda`; the +system-default `nvcc` may point at a different toolkit. OBLITERATUS fails before +weight allocation if either extension cannot be imported. + +The complete Qwen hybrid text model must fit one CUDA device with 15% free-memory +headroom. Generic `device_map="auto"` layer sharding is deliberately disabled for +this architecture because the recurrent-state execution path has not been +validated across devices. + Use the virtual environment interpreter directly after replacing Torch. A subsequent `uv run` or `uv sync` without the CUDA override may restore the portable CPU wheel from the lock. The agentic developer installer automates this ordering and performs diff --git a/obliteratus/models/loader.py b/obliteratus/models/loader.py index 475026a..27e8244 100644 --- a/obliteratus/models/loader.py +++ b/obliteratus/models/loader.py @@ -2,6 +2,7 @@ from __future__ import annotations +import importlib import logging import os import re @@ -19,6 +20,7 @@ from obliteratus.runtime_contracts import ( effective_model_memory_gb, quantized_model_fits_gpu, resolve_model_load_policy, + select_single_cuda_device, should_snapshot_model, validate_model_load_request, ) @@ -619,6 +621,40 @@ def _available_gpu_memory_gb() -> float: return dev.get_total_free_gb() +def _require_qwen_hybrid_kernels() -> None: + """Fail before allocation when Qwen's validated DeltaNet kernels are absent.""" + missing = [] + for package, module_name in ( + ("flash-linear-attention", "fla"), + ("causal-conv1d", "causal_conv1d"), + ): + try: + importlib.import_module(module_name) + except (ImportError, OSError, RuntimeError): + missing.append(package) + if missing: + raise RuntimeError( + "Qwen3.5/Qwen3.8 requires its validated CUDA DeltaNet fast path; " + f"missing or incompatible: {', '.join(missing)}. Install a PyTorch/CUDA-" + "compatible build with `pip install -e '.[qwen-hybrid]'`. The generic " + "PyTorch fallback is not used because it produced invalid pristine logits." + ) + + +def _qwen_single_device(estimate_gb: float, quantization: str | None) -> int: + """Choose one CUDA device for a Qwen hybrid text model.""" + if not dev.is_cuda() or torch.cuda.device_count() <= 0: + raise RuntimeError("Qwen3.5/Qwen3.8 validated surgery requires an NVIDIA CUDA device") + free_gb = [ + torch.cuda.mem_get_info(index)[0] / (1024 ** 3) + for index in range(torch.cuda.device_count()) + ] + return select_single_cuda_device( + free_gb, + effective_model_memory_gb(estimate_gb, quantization), + ) + + def _hf_token() -> str | None: """Resolve the optional Hugging Face read token.""" return resolve_secret("HF_TOKEN") @@ -809,20 +845,25 @@ def load_model( if task == "classification": config.num_labels = num_labels load_kwargs["config"] = config - if task == "causal_lm" and getattr(config, "model_type", "") == "qwen3_5": + is_qwen_hybrid = task == "causal_lm" and getattr(config, "model_type", "") == "qwen3_5" + qwen_device_index = None + if is_qwen_hybrid: import transformers version_match = re.match(r"^(\d+)\.(\d+)", transformers.__version__) version = tuple(map(int, version_match.groups())) if version_match else (0, 0) - if version < (5, 15): + if version < (5, 8): raise RuntimeError( - "Qwen3.8 requires transformers>=5.15 for the validated hybrid " - "runtime and multidimensional-position fixes" + "Qwen3.8 requires transformers>=5.8 for the validated hybrid runtime" ) + _require_qwen_hybrid_kernels() + qwen_device_index = _qwen_single_device(est_gb, quantization) load_kwargs["attn_implementation"] = "sdpa" logger.info( "Loading Qwen3.8 through AutoModelForCausalLM as an explicit text-only " - "derivative; vision and MTP checkpoint tensors are not part of the output." + "derivative on cuda:%d; vision and MTP checkpoint tensors are not part " + "of the output.", + qwen_device_index, ) # Quantization support (requires bitsandbytes) @@ -866,6 +907,11 @@ def load_model( if "device_map" not in load_kwargs and load_policy.use_device_map_auto: load_kwargs["device_map"] = "auto" + if qwen_device_index is not None: + # Generic layer sharding is not a validated execution path for the + # recurrent DeltaNet state. Keep the complete text model on one GPU. + load_kwargs["device_map"] = {"": qwen_device_index} + # Offload support: provide a folder for disk offloading when GPU memory is insufficient _offload_dir = None _owns_offload_dir = False diff --git a/obliteratus/runtime_contracts.py b/obliteratus/runtime_contracts.py index f4fc8e6..8fbfc99 100644 --- a/obliteratus/runtime_contracts.py +++ b/obliteratus/runtime_contracts.py @@ -180,6 +180,39 @@ def effective_model_memory_gb(estimate_gb: float, quantization: str | None) -> f return estimate_gb / factor +def select_single_cuda_device( + free_memory_gb: Collection[float], + required_memory_gb: float, + *, + max_utilization: float = 0.85, +) -> int: + """Select one CUDA device that can hold a model with bounded headroom. + + Hybrid recurrent models cannot be assumed safe under Accelerate's generic + layer sharding. Selection is deterministic: choose the device with the + most free memory, then the lowest index on ties. + """ + if not math.isfinite(required_memory_gb) or required_memory_gb <= 0: + raise ValueError("required_memory_gb must be a positive finite number") + if not math.isfinite(max_utilization) or not 0 < max_utilization <= 1: + raise ValueError("max_utilization must be in (0, 1]") + candidates = [ + (float(free_gb), index) + for index, free_gb in enumerate(free_memory_gb) + if math.isfinite(float(free_gb)) + and float(free_gb) > 0 + and required_memory_gb <= float(free_gb) * max_utilization + ] + if not candidates: + available = ", ".join(f"cuda:{i}={float(value):.1f} GiB" for i, value in enumerate(free_memory_gb)) + raise RuntimeError( + "Qwen3.5/Qwen3.8 requires the complete text model on one CUDA device; " + f"need {required_memory_gb:.1f} GiB with headroom, available: " + f"{available or 'no CUDA devices'}" + ) + return max(candidates, key=lambda item: (item[0], -item[1]))[1] + + def quantized_model_fits_gpu( estimate_gb: float, quantization: str | None, diff --git a/pyproject.toml b/pyproject.toml index 3cc3634..eaa2180 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -51,6 +51,11 @@ dev = [ "ruff==0.16.2", ] quantization = ["bitsandbytes>=0.46.1"] +qwen-hybrid = [ + "transformers>=5.8", + "flash-linear-attention>=0.5.2", + "causal-conv1d>=1.7.0", +] spaces = ["gradio>=6.7,<7.0"] [dependency-groups] diff --git a/tests/test_loader_boundaries.py b/tests/test_loader_boundaries.py index 9e8efe2..831f713 100644 --- a/tests/test_loader_boundaries.py +++ b/tests/test_loader_boundaries.py @@ -512,19 +512,67 @@ def test_mistral3_load_uses_image_text_class_and_keeps_remote_code_disabled( loader_boundary.model_class.from_pretrained.assert_not_called() -def test_qwen35_load_is_explicit_text_only_sdpa(loader_boundary, caplog): +def test_qwen35_load_is_explicit_text_only_sdpa( + loader_boundary, caplog, monkeypatch, +): caplog.set_level("INFO") loader_boundary.config.model_type = "qwen3_5" loader_boundary.config.architectures = ["Qwen3_5ForConditionalGeneration"] + monkeypatch.setattr(loader, "_require_qwen_hybrid_kernels", Mock()) + monkeypatch.setattr(loader, "_qwen_single_device", Mock(return_value=2)) handle = loader.load_model("Qwen/Qwen3.8-27B", skip_snapshot=True) assert handle.architecture == "qwen3_5" kwargs = loader_boundary.model_class.from_pretrained.call_args.kwargs assert kwargs["attn_implementation"] == "sdpa" + assert kwargs["device_map"] == {"": 2} + assert "device_map" not in kwargs or kwargs["device_map"] != "auto" assert "text-only derivative" in caplog.text +def test_qwen35_missing_fast_kernels_fails_before_weight_load( + loader_boundary, monkeypatch, +): + loader_boundary.config.model_type = "qwen3_5" + monkeypatch.setattr( + loader, + "_require_qwen_hybrid_kernels", + Mock(side_effect=RuntimeError("missing or incompatible: causal-conv1d")), + ) + + with pytest.raises(RuntimeError, match="causal-conv1d"): + loader.load_model("Qwen/Qwen3.8-27B", skip_snapshot=True) + + loader_boundary.model_class.from_pretrained.assert_not_called() + + +def test_qwen_kernel_probe_reports_each_incompatible_extension(monkeypatch): + def import_module(name): + if name == "fla": + raise ImportError("missing") + raise OSError("ABI mismatch") + + monkeypatch.setattr(loader.importlib, "import_module", import_module) + + with pytest.raises(RuntimeError, match="flash-linear-attention, causal-conv1d"): + loader._require_qwen_hybrid_kernels() + + +def test_qwen_single_device_uses_per_device_free_memory(monkeypatch): + monkeypatch.setattr(loader.dev, "is_cuda", lambda: True) + monkeypatch.setattr(loader.torch.cuda, "device_count", lambda: 3) + free = [40, 76, 72] + monkeypatch.setattr( + loader.torch.cuda, + "mem_get_info", + lambda index: (free[index] * 1024 ** 3, 80 * 1024 ** 3), + ) + + assert loader._qwen_single_device(54.0, None) == 1 + assert loader._qwen_single_device(216.0, "4bit") == 1 + + def test_model_handle_metadata_snapshot_restore_summary_and_cleanup(tmp_path): model = _model() nested = SimpleNamespace( diff --git a/tests/test_runtime_contracts.py b/tests/test_runtime_contracts.py index 53382d6..d080fa9 100644 --- a/tests/test_runtime_contracts.py +++ b/tests/test_runtime_contracts.py @@ -15,6 +15,7 @@ from obliteratus.runtime_contracts import ( norm_restoration_ratio, quantized_model_fits_gpu, resolve_model_load_policy, + select_single_cuda_device, should_snapshot_model, supports_bfloat16_target, validate_model_load_request, @@ -499,3 +500,26 @@ def test_bfloat16_capability_boundaries( cuda_major=cuda_major, torch_version=version, ) is expected + + +def test_single_cuda_device_prefers_most_free_memory_then_lowest_index(): + assert select_single_cuda_device([64.0, 80.0, 80.0], 54.0) == 1 + + +def test_single_cuda_device_requires_one_device_to_fit_with_headroom(): + with pytest.raises(RuntimeError, match=r"complete text model on one CUDA device"): + select_single_cuda_device([60.0, 63.0], 54.0) + + +@pytest.mark.parametrize( + ("required", "utilization", "message"), + [ + (0.0, 0.85, "required_memory_gb"), + (float("nan"), 0.85, "required_memory_gb"), + (1.0, 0.0, "max_utilization"), + (1.0, 1.1, "max_utilization"), + ], +) +def test_single_cuda_device_rejects_invalid_policy(required, utilization, message): + with pytest.raises(ValueError, match=message): + select_single_cuda_device([80.0], required, max_utilization=utilization) diff --git a/uv.lock b/uv.lock index b0f32b7..df1df41 100644 --- a/uv.lock +++ b/uv.lock @@ -15,7 +15,7 @@ resolution-markers = [ ] [options] -exclude-newer = "2026-08-19T00:05:48.361273363Z" +exclude-newer = "2026-08-24T05:10:12.868014219Z" exclude-newer-span = "P3D" [manifest] @@ -410,6 +410,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/84/c2/80633736cd183ee4a62107413def345f7e6e3c01563dbca1417363cf957e/build-1.2.2.post1-py3-none-any.whl", hash = "sha256:1d61c0887fa860c01971625baae8bdd338e517b836a2f70dd1f7aa3a6b2fc5b5", size = 22950, upload-time = "2024-10-06T17:22:23.299Z" }, ] +[[package]] +name = "causal-conv1d" +version = "1.7.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "ninja" }, + { name = "packaging" }, + { name = "torch", version = "2.13.0", source = { registry = "https://pypi.org/simple" }, marker = "sys_platform != 'linux' and sys_platform != 'win32'" }, + { name = "torch", version = "2.13.0+cpu", source = { registry = "https://download.pytorch.org/whl/cpu" }, marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f4/d1/eba27735f31bd5527d39a3c7f693e6ded7b844cab275db719a15ad37b6cd/causal_conv1d-1.7.0.tar.gz", hash = "sha256:3202758494eaa7b597ce1c282dfa188889506bfcb92cad3c407d26736bfcd32b", size = 30328, upload-time = "2026-08-20T10:54:15.808Z" } + [[package]] name = "certifi" version = "2026.7.22" @@ -976,6 +988,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/32/91/30151a39f7570f448ed84529390628a651d7f27c87d73c9b887f8189695e/docutils-0.23-py3-none-any.whl", hash = "sha256:25d013af9bf23bc1c7b2b093dff4208166c53a94786c9e447808335ef1185fea", size = 634701, upload-time = "2026-05-27T17:40:58.442Z" }, ] +[[package]] +name = "einops" +version = "0.8.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2c/77/850bef8d72ffb9219f0b1aac23fbc1bf7d038ee6ea666f331fa273031aa2/einops-0.8.2.tar.gz", hash = "sha256:609da665570e5e265e27283aab09e7f279ade90c4f01bcfca111f3d3e13f2827", size = 56261, upload-time = "2026-01-26T04:13:17.638Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2a/09/f8d8f8f31e4483c10a906437b4ce31bdf3d6d417b73fe33f1a8b59e34228/einops-0.8.2-py3-none-any.whl", hash = "sha256:54058201ac7087911181bfec4af6091bb59380360f069276601256a76af08193", size = 65638, upload-time = "2026-01-26T04:13:18.546Z" }, +] + [[package]] name = "exceptiongroup" version = "1.3.1" @@ -1013,6 +1034,31 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/c1/e8/72f8cef9fdfeffe06213fe8508039396ee48daa0e3259457ed766173bfd6/filelock-3.32.2-py3-none-any.whl", hash = "sha256:87dd94cf281e586d135fa51132b8e3d9a598b316e90377a288663c9321036c82", size = 98830, upload-time = "2026-07-29T22:46:03.52Z" }, ] +[[package]] +name = "fla-core" +version = "0.5.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "einops" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/94/85/20bdc0fbbbaeec27b5b198e0dbbbc51161a267066a4aebd9e4b413637c6c/fla_core-0.5.2.tar.gz", hash = "sha256:9360fc412f784c1c8f05c320ec2902d5d968764178c9b8a92efc919e17a39680", size = 587835, upload-time = "2026-07-27T18:26:19.424Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2d/ed/dfe19c4da779957eb6a42a26812f9b4e2280bf757a17a71933ff59ffcb98/fla_core-0.5.2-py3-none-any.whl", hash = "sha256:5e830c85bad3d0d34677f98ac7074d08687a3756f0f0499d95ceb96eb6920761", size = 819225, upload-time = "2026-07-27T18:26:16.147Z" }, +] + +[[package]] +name = "flash-linear-attention" +version = "0.5.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "fla-core" }, + { name = "transformers" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d2/76/c180949eae5161b9fcf4c928cab52f0c257ce84c1a59b4462b1d2b6e5883/flash_linear_attention-0.5.2.tar.gz", hash = "sha256:c053d3a75c8f5b725063f719ae6bce0f4d9dac6da32735be8ae7d485a6c9820f", size = 208110, upload-time = "2026-07-27T18:26:20.536Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/90/d2/2070e3cf2148c5cce99ca4876633c4b7b89ec085323600bd3d47aeacd306/flash_linear_attention-0.5.2-py3-none-any.whl", hash = "sha256:dcf405d81f5426393b59037097aa700d0f4a841465d5028d5aa543f4502f2400", size = 399590, upload-time = "2026-07-27T18:26:17.912Z" }, +] + [[package]] name = "fonttools" version = "4.63.0" @@ -2366,6 +2412,32 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/7b/e5/7cafee2f0413ca4cb0ef3bd111e94d408a48810008b283ad8aee00dd1809/nh3-0.3.6-cp38-abi3-win_arm64.whl", hash = "sha256:69f365963f63a1e9bff53bdbb3c542c7c2efed3e163c9d5d83a772a2ac468c21", size = 603060, upload-time = "2026-06-22T00:47:00.596Z" }, ] +[[package]] +name = "ninja" +version = "1.13.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/43/73/79a0b22fc731989c708068427579e840a6cf4e937fe7ae5c5d0b7356ac22/ninja-1.13.0.tar.gz", hash = "sha256:4a40ce995ded54d9dc24f8ea37ff3bf62ad192b547f6c7126e7e25045e76f978", size = 242558, upload-time = "2025-08-11T15:10:19.421Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3c/74/d02409ed2aa865e051b7edda22ad416a39d81a84980f544f8de717cab133/ninja-1.13.0-py3-none-macosx_10_9_universal2.whl", hash = "sha256:fa2a8bfc62e31b08f83127d1613d10821775a0eb334197154c4d6067b7068ff1", size = 310125, upload-time = "2025-08-11T15:09:50.971Z" }, + { url = "https://files.pythonhosted.org/packages/8e/de/6e1cd6b84b412ac1ef327b76f0641aeb5dcc01e9d3f9eee0286d0c34fd93/ninja-1.13.0-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:3d00c692fb717fd511abeb44b8c5d00340c36938c12d6538ba989fe764e79630", size = 177467, upload-time = "2025-08-11T15:09:52.767Z" }, + { url = "https://files.pythonhosted.org/packages/c8/83/49320fb6e58ae3c079381e333575fdbcf1cca3506ee160a2dcce775046fa/ninja-1.13.0-py3-none-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:be7f478ff9f96a128b599a964fc60a6a87b9fa332ee1bd44fa243ac88d50291c", size = 187834, upload-time = "2025-08-11T15:09:54.115Z" }, + { url = "https://files.pythonhosted.org/packages/56/c7/ba22748fb59f7f896b609cd3e568d28a0a367a6d953c24c461fe04fc4433/ninja-1.13.0-py3-none-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:60056592cf495e9a6a4bea3cd178903056ecb0943e4de45a2ea825edb6dc8d3e", size = 202736, upload-time = "2025-08-11T15:09:55.745Z" }, + { url = "https://files.pythonhosted.org/packages/79/22/d1de07632b78ac8e6b785f41fa9aad7a978ec8c0a1bf15772def36d77aac/ninja-1.13.0-py3-none-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:1c97223cdda0417f414bf864cfb73b72d8777e57ebb279c5f6de368de0062988", size = 179034, upload-time = "2025-08-11T15:09:57.394Z" }, + { url = "https://files.pythonhosted.org/packages/ed/de/0e6edf44d6a04dabd0318a519125ed0415ce437ad5a1ec9b9be03d9048cf/ninja-1.13.0-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fb46acf6b93b8dd0322adc3a4945452a4e774b75b91293bafcc7b7f8e6517dfa", size = 180716, upload-time = "2025-08-11T15:09:58.696Z" }, + { url = "https://files.pythonhosted.org/packages/54/28/938b562f9057aaa4d6bfbeaa05e81899a47aebb3ba6751e36c027a7f5ff7/ninja-1.13.0-py3-none-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:4be9c1b082d244b1ad7ef41eb8ab088aae8c109a9f3f0b3e56a252d3e00f42c1", size = 146843, upload-time = "2025-08-11T15:10:00.046Z" }, + { url = "https://files.pythonhosted.org/packages/2a/fb/d06a3838de4f8ab866e44ee52a797b5491df823901c54943b2adb0389fbb/ninja-1.13.0-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:6739d3352073341ad284246f81339a384eec091d9851a886dfa5b00a6d48b3e2", size = 154402, upload-time = "2025-08-11T15:10:01.657Z" }, + { url = "https://files.pythonhosted.org/packages/31/bf/0d7808af695ceddc763cf251b84a9892cd7f51622dc8b4c89d5012779f06/ninja-1.13.0-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:11be2d22027bde06f14c343f01d31446747dbb51e72d00decca2eb99be911e2f", size = 552388, upload-time = "2025-08-11T15:10:03.349Z" }, + { url = "https://files.pythonhosted.org/packages/9d/70/c99d0c2c809f992752453cce312848abb3b1607e56d4cd1b6cded317351a/ninja-1.13.0-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:aa45b4037b313c2f698bc13306239b8b93b4680eb47e287773156ac9e9304714", size = 472501, upload-time = "2025-08-11T15:10:04.735Z" }, + { url = "https://files.pythonhosted.org/packages/9f/43/c217b1153f0e499652f5e0766da8523ce3480f0a951039c7af115e224d55/ninja-1.13.0-py3-none-musllinux_1_2_i686.whl", hash = "sha256:5f8e1e8a1a30835eeb51db05cf5a67151ad37542f5a4af2a438e9490915e5b72", size = 638280, upload-time = "2025-08-11T15:10:06.512Z" }, + { url = "https://files.pythonhosted.org/packages/8c/45/9151bba2c8d0ae2b6260f71696330590de5850e5574b7b5694dce6023e20/ninja-1.13.0-py3-none-musllinux_1_2_ppc64le.whl", hash = "sha256:3d7d7779d12cb20c6d054c61b702139fd23a7a964ec8f2c823f1ab1b084150db", size = 642420, upload-time = "2025-08-11T15:10:08.35Z" }, + { url = "https://files.pythonhosted.org/packages/3c/fb/95752eb635bb8ad27d101d71bef15bc63049de23f299e312878fc21cb2da/ninja-1.13.0-py3-none-musllinux_1_2_riscv64.whl", hash = "sha256:d741a5e6754e0bda767e3274a0f0deeef4807f1fec6c0d7921a0244018926ae5", size = 585106, upload-time = "2025-08-11T15:10:09.818Z" }, + { url = "https://files.pythonhosted.org/packages/c1/31/aa56a1a286703800c0cbe39fb4e82811c277772dc8cd084f442dd8e2938a/ninja-1.13.0-py3-none-musllinux_1_2_s390x.whl", hash = "sha256:e8bad11f8a00b64137e9b315b137d8bb6cbf3086fbdc43bf1f90fd33324d2e96", size = 707138, upload-time = "2025-08-11T15:10:11.366Z" }, + { url = "https://files.pythonhosted.org/packages/34/6f/5f5a54a1041af945130abdb2b8529cbef0cdcbbf9bcf3f4195378319d29a/ninja-1.13.0-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:b4f2a072db3c0f944c32793e91532d8948d20d9ab83da9c0c7c15b5768072200", size = 581758, upload-time = "2025-08-11T15:10:13.295Z" }, + { url = "https://files.pythonhosted.org/packages/95/97/51359c77527d45943fe7a94d00a3843b81162e6c4244b3579fe8fc54cb9c/ninja-1.13.0-py3-none-win32.whl", hash = "sha256:8cfbb80b4a53456ae8a39f90ae3d7a2129f45ea164f43fadfa15dc38c4aef1c9", size = 267201, upload-time = "2025-08-11T15:10:15.158Z" }, + { url = "https://files.pythonhosted.org/packages/29/45/c0adfbfb0b5895aa18cec400c535b4f7ff3e52536e0403602fc1a23f7de9/ninja-1.13.0-py3-none-win_amd64.whl", hash = "sha256:fb8ee8719f8af47fed145cced4a85f0755dd55d45b2bddaf7431fa89803c5f3e", size = 309975, upload-time = "2025-08-11T15:10:16.697Z" }, + { url = "https://files.pythonhosted.org/packages/df/93/a7b983643d1253bb223234b5b226e69de6cda02b76cdca7770f684b795f5/ninja-1.13.0-py3-none-win_arm64.whl", hash = "sha256:3c0b40b1f0bba764644385319028650087b4c1b18cdfa6f45cb39a3669b81aa9", size = 290806, upload-time = "2025-08-11T15:10:18.018Z" }, +] + [[package]] name = "numpy" version = "2.2.6" @@ -2633,6 +2705,11 @@ dev = [ quantization = [ { name = "bitsandbytes" }, ] +qwen-hybrid = [ + { name = "causal-conv1d" }, + { name = "flash-linear-attention" }, + { name = "transformers" }, +] spaces = [ { name = "gradio" }, ] @@ -2656,7 +2733,9 @@ requires-dist = [ { name = "accelerate", specifier = ">=0.24" }, { name = "bitsandbytes", marker = "extra == 'quantization'", specifier = ">=0.46.1" }, { name = "build", marker = "extra == 'dev'", specifier = "==1.2.2.post1" }, + { name = "causal-conv1d", marker = "extra == 'qwen-hybrid'", specifier = ">=1.7.0" }, { name = "datasets", specifier = ">=2.14" }, + { name = "flash-linear-attention", marker = "extra == 'qwen-hybrid'", specifier = ">=0.5.2" }, { name = "gradio", marker = "extra == 'spaces'", specifier = ">=6.7,<7.0" }, { name = "hypothesis", marker = "extra == 'dev'", specifier = "==6.165.3" }, { name = "matplotlib", specifier = ">=3.7" }, @@ -2675,8 +2754,9 @@ requires-dist = [ { name = "torch", marker = "sys_platform == 'linux' or sys_platform == 'win32'", specifier = ">=2.0", index = "https://download.pytorch.org/whl/cpu" }, { name = "tqdm", specifier = ">=4.64" }, { name = "transformers", specifier = ">=4.40" }, + { name = "transformers", marker = "extra == 'qwen-hybrid'", specifier = ">=5.8" }, ] -provides-extras = ["dev", "quantization", "spaces"] +provides-extras = ["dev", "quantization", "qwen-hybrid", "spaces"] [package.metadata.requires-dev] ci = [