diff --git a/megatron_runtime/pyproject.toml b/megatron_runtime/pyproject.toml index 2525f9e73..fe03a3ed8 100644 --- a/megatron_runtime/pyproject.toml +++ b/megatron_runtime/pyproject.toml @@ -22,7 +22,7 @@ dependencies = [ "nvidia-ml-py==13.580.82", "nvidia-modelopt>=0.42.0a0", "nvidia-resiliency-ext<0.5", - "openai>=2.14.0", + "openai>=2.14.0,<3", "peft>=0.14.0", "polars>=1.26.0", "pydantic>=2.12", diff --git a/megatron_runtime/uv.lock b/megatron_runtime/uv.lock index 2cec383fa..16b445e71 100644 --- a/megatron_runtime/uv.lock +++ b/megatron_runtime/uv.lock @@ -344,7 +344,7 @@ requires-dist = [ { name = "nvidia-modelopt", specifier = ">=0.42.0a0" }, { name = "nvidia-nccl-cu13", marker = "extra == 'cuda13'", specifier = "==2.28.9" }, { name = "nvidia-resiliency-ext", specifier = "<0.5" }, - { name = "openai", specifier = ">=2.14.0" }, + { name = "openai", specifier = ">=2.14.0,<3" }, { name = "peft", specifier = ">=0.14.0" }, { name = "polars", specifier = ">=1.26.0" }, { name = "pybind11", specifier = ">=2.13.6" }, @@ -1614,19 +1614,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" }, ] -[[package]] -name = "httpcore2" -version = "2.12.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "h11" }, - { name = "truststore" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/be/ad/f4f0e57345f1870f3e8cb624e058d7eca6e5a27d33bcc3311d9b618734cd/httpcore2-2.12.0.tar.gz", hash = "sha256:9293522bba0aa7c4c8e9e3f040c16575bd8868e155a77fa30c7a9085a5eae648", size = 67548, upload-time = "2026-08-18T13:22:08.211Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d2/74/d370e55600d9bcfa0d9794b0166126d49291a3d2b20c268fc98c453a4948/httpcore2-2.12.0-py3-none-any.whl", hash = "sha256:7e04258ce01013d7d615e5b910a3b27fac937d7a95038227e79652b4ba3b4ceb", size = 83074, upload-time = "2026-08-18T13:22:05.854Z" }, -] - [[package]] name = "httpx" version = "0.28.1" @@ -1642,32 +1629,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" }, ] -[[package]] -name = "httpx2" -version = "2.12.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "anyio", marker = "sys_platform != 'emscripten' or (extra == 'extra-20-art-megatron-runtime-cuda12' and extra == 'extra-20-art-megatron-runtime-cuda13')" }, - { name = "httpcore2", marker = "sys_platform != 'emscripten' or (extra == 'extra-20-art-megatron-runtime-cuda12' and extra == 'extra-20-art-megatron-runtime-cuda13')" }, - { name = "httpx2-jsfetch", marker = "sys_platform == 'emscripten' or (extra == 'extra-20-art-megatron-runtime-cuda12' and extra == 'extra-20-art-megatron-runtime-cuda13')" }, - { name = "idna" }, - { name = "truststore", marker = "sys_platform != 'emscripten' or (extra == 'extra-20-art-megatron-runtime-cuda12' and extra == 'extra-20-art-megatron-runtime-cuda13')" }, - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/7f/f8/579a8b51e42e38ee32647df9f08aa25643ae788e275cc625b199829c4671/httpx2-2.12.0.tar.gz", hash = "sha256:7631fe9887a8a2275f4a2540e053aa670fcc50742864a9ae7c66e609fdcf12cf", size = 100040, upload-time = "2026-08-18T13:22:09.086Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c8/95/411ba65569158e862368917aaf56597f3e5fa3b91b0502919638465a08f3/httpx2-2.12.0-py3-none-any.whl", hash = "sha256:cc8b6eecb8661c146b8f89a60e97456ee086e91a784ed31ac450c3a9e613dd36", size = 95427, upload-time = "2026-08-18T13:22:06.834Z" }, -] - -[[package]] -name = "httpx2-jsfetch" -version = "1.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/cd/c4/0e5636363151a2a1795e0a77617168b9ca438e1748ec05fc9b5687f93d64/httpx2_jsfetch-1.0.tar.gz", hash = "sha256:70a0e3eabfef7cce5ad9c629f7d01ca05e418f586646f4ddf14782e4c1454c60", size = 6872, upload-time = "2026-08-07T00:13:07.492Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/9b/43/832f631d32e4f1211caa2ba368317739fe71f0b8530e4c9d15dc454bac2a/httpx2_jsfetch-1.0-py3-none-any.whl", hash = "sha256:cb916b707601e69a07721aabc8f3f6659be3a6893bc1ff5c6f9e02241df2da32", size = 6382, upload-time = "2026-08-07T00:13:06.567Z" }, -] - [[package]] name = "huey" version = "3.3.4" @@ -3093,20 +3054,21 @@ wheels = [ [[package]] name = "openai" -version = "3.3.0" +version = "2.54.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio" }, { name = "distro" }, - { name = "httpx2" }, + { name = "httpx" }, { name = "jiter" }, { name = "pydantic" }, { name = "sniffio" }, + { name = "tqdm" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/e6/6c/670271205379061112bf5ed900b78a68e21bf11733de829772c5322f92aa/openai-3.3.0.tar.gz", hash = "sha256:28f904a9fbff15288e9dd67bc0f7020d4f576d37786383a480db02fe5d8139d3", size = 1166178, upload-time = "2026-08-18T21:17:53.419Z" } +sdist = { url = "https://files.pythonhosted.org/packages/50/9a/8c75e8c8a5b407a0586faeb2afac91674ff955c191ecc1d6d3b6669f6788/openai-2.54.0.tar.gz", hash = "sha256:e3e6f8bc1ba30ddf381ace1a14340eed381cb984a1a59bd0f34b5be3b5d49cfa", size = 1100285, upload-time = "2026-08-11T18:46:59.035Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/82/83/d44980712bf51dddaca090cd772b3102e75377ddac877eaa4b2cb58d886a/openai-3.3.0-py3-none-any.whl", hash = "sha256:ded6b2112e6d299c7a2573ff6f165dc92fb64ceaa4d7daa42345f091157bd373", size = 1690265, upload-time = "2026-08-18T21:17:51.351Z" }, + { url = "https://files.pythonhosted.org/packages/64/a8/bb76c7356de8ad57f59d5ff993d434df0607f07f08bcc9c9a5c275e399c0/openai-2.54.0-py3-none-any.whl", hash = "sha256:89089789197ccdb87f173a03145ed1598d00795220c93e96cf712b1cbf5e5f2b", size = 1660351, upload-time = "2026-08-11T18:46:56.684Z" }, ] [[package]] @@ -4689,15 +4651,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/c4/6f/fb96d15db6f36d6eae4cafb998c2e0353bf59d7c4ea1662d7497f269134a/triton-3.7.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7e40869937a68206ec70d7f25bb7ec6433cb083f9135e1f36dbd318dc449a728", size = 197719725, upload-time = "2026-06-17T19:53:20.419Z" }, ] -[[package]] -name = "truststore" -version = "0.10.4" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/53/a3/1585216310e344e8102c22482f6060c7a6ea0322b63e026372e6dcefcfd6/truststore-0.10.4.tar.gz", hash = "sha256:9d91bd436463ad5e4ee4aba766628dd6cd7010cf3e2461756b3303710eebc301", size = 26169, upload-time = "2025-08-12T18:49:02.73Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/19/97/56608b2249fe206a67cd573bc93cd9896e1efb9e98bce9c163bcdc704b88/truststore-0.10.4-py3-none-any.whl", hash = "sha256:adaeaecf1cbb5f4de3b1959b42d41f6fab57b2b1666adb59e89cb0b53361d981", size = 18660, upload-time = "2025-08-12T18:49:01.46Z" }, -] - [[package]] name = "typer" version = "0.27.1" diff --git a/pyproject.toml b/pyproject.toml index 3a879c612..8d0d487bd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,7 +7,7 @@ requires-python = ">=3.12" dependencies = [ "aiohttp>=3.10.0", "anthropic>=0.77.0", - "openai>=2.14.0", + "openai>=2.14.0,<3", "pydantic>=2.12", "requests>=2.32.0", "typing-extensions>=4.13", diff --git a/src/art/megatron/model_support/registry.py b/src/art/megatron/model_support/registry.py index d9ef70ad1..ef6225c47 100644 --- a/src/art/megatron/model_support/registry.py +++ b/src/art/megatron/model_support/registry.py @@ -161,6 +161,7 @@ "Qwen/Qwen3.5-4B", "Qwen/Qwen3.5-27B", "Qwen/Qwen3.6-27B", + "Qwen/Qwen3.8-27B", ), default_target_modules=_QWEN3_5_DENSE_TARGET_MODULES, native_vllm_lora_status=_VALIDATED_NATIVE_VLLM_LORA_STATUS, diff --git a/src/art/trainer_rank/_impl.py b/src/art/trainer_rank/_impl.py index 92022bdc1..9a97426a0 100644 --- a/src/art/trainer_rank/_impl.py +++ b/src/art/trainer_rank/_impl.py @@ -1417,7 +1417,7 @@ def _validate_checkpoint_adapter_config( raise TypeError( "adapter_config['base_model_name_or_path'] must be a string" ) - if base_model.startswith(("Qwen/Qwen3.5-", "Qwen/Qwen3.6-")): + if base_model.startswith(("Qwen/Qwen3.5-", "Qwen/Qwen3.6-", "Qwen/Qwen3.8-")): dimensions = { "num_attention_heads": getattr( self.runtime.provider, "num_attention_heads", None diff --git a/tests/integration/megatron/model_support/gdn_fp32_reference.py b/tests/integration/megatron/model_support/gdn_fp32_reference.py index b9049e931..eaddc6d43 100644 --- a/tests/integration/megatron/model_support/gdn_fp32_reference.py +++ b/tests/integration/megatron/model_support/gdn_fp32_reference.py @@ -224,8 +224,10 @@ def install_megatron_qwen35_gdn_fp32_reference( *, base_model: str, ) -> None: - model_key = base_model.lower() - if "qwen3.5" not in model_key and "qwen3_5" not in model_key: + from art.megatron.model_support.registry import get_model_support_handler + + handler = get_model_support_handler(base_model) + if handler.key not in {"qwen3_5_dense", "qwen3_5_moe"}: return from art.megatron.gdn import operator as gdn_operator diff --git a/tests/integration/megatron/model_support/hf_parity_worker.py b/tests/integration/megatron/model_support/hf_parity_worker.py index f9bf5419e..9f24a8596 100644 --- a/tests/integration/megatron/model_support/hf_parity_worker.py +++ b/tests/integration/megatron/model_support/hf_parity_worker.py @@ -1109,8 +1109,10 @@ def _hf_prefix_tree_paths( def _install_hf_qwen35_gdn_fp32_reference(model: Any, *, base_model: str) -> None: - model_key = base_model.lower() - if "qwen3.5" not in model_key and "qwen3_5" not in model_key: + from art.megatron.model_support.registry import get_model_support_handler + + handler = get_model_support_handler(base_model) + if handler.key not in {"qwen3_5_dense", "qwen3_5_moe"}: return patched = 0 for module in model.modules(): diff --git a/tests/integration/megatron/model_support/test_provider_support.py b/tests/integration/megatron/model_support/test_provider_support.py index 838e85191..1731162c4 100644 --- a/tests/integration/megatron/model_support/test_provider_support.py +++ b/tests/integration/megatron/model_support/test_provider_support.py @@ -137,6 +137,16 @@ def test_openpipe_qwen3_14b_instruct_uses_qwen3_dense_support() -> None: assert handler.key == "qwen3_dense" +def test_qwen38_27b_uses_qwen35_dense_support() -> None: + spec = get_model_support_spec("Qwen/Qwen3.8-27B") + handler = get_model_support_handler("Qwen/Qwen3.8-27B") + + assert spec.key == "qwen3_5_dense" + assert spec.is_moe is False + assert spec.native_vllm_lora_status == "validated" + assert handler.key == "qwen3_5_dense" + + def test_meta_llama_32_1b_instruct_uses_llama3_dense_support() -> None: model = "meta-llama/Llama-3.2-1B-Instruct" spec = get_model_support_spec(model) diff --git a/tests/integration/megatron/model_support/test_workflow.py b/tests/integration/megatron/model_support/test_workflow.py index 790c57b02..be0aae079 100644 --- a/tests/integration/megatron/model_support/test_workflow.py +++ b/tests/integration/megatron/model_support/test_workflow.py @@ -885,6 +885,22 @@ def test_validated_architecture_representative_models_are_fixed() -> None: ] +def test_qwen38_uses_its_measured_throughput_fingerprint() -> None: + qwen35 = handler_workflow_resources_for_base_model("Qwen/Qwen3.5-27B") + qwen38 = handler_workflow_resources_for_base_model("Qwen/Qwen3.8-27B") + assert qwen35 is not None and qwen35.e2e_throughput is not None + assert qwen38 is not None and qwen38.e2e_throughput is not None + qwen35_config = qwen35.e2e_throughput.throughput + qwen38_config = qwen38.e2e_throughput.throughput + assert qwen35_config is not None and qwen38_config is not None + assert qwen35_config.thresholds["b300"].calibration_fingerprint == ( + "5617e8880591545a3281ff14d1fe5197eeefc21a81ec80d1a107fd31421d37a0" + ) + assert qwen38_config.thresholds["b300"].calibration_fingerprint == ( + "b07ee7ec6338ec021463a43a90fc96c5c5a036b4a04d90b80e1d22c1eef86774" + ) + + def test_dsv4_runtime_stages_use_full_model_resources() -> None: resources = handler_workflow_resources_for_base_model( "deepseek-ai/DeepSeek-V4-Flash" diff --git a/tests/integration/megatron/model_support/workflow_fixtures.py b/tests/integration/megatron/model_support/workflow_fixtures.py index 4045cd9c9..1468e3af3 100644 --- a/tests/integration/megatron/model_support/workflow_fixtures.py +++ b/tests/integration/megatron/model_support/workflow_fixtures.py @@ -52,6 +52,7 @@ "Qwen/Qwen3-32B": "9216db5781bf21249d130ec9da846c4624c16137", "Qwen/Qwen3-30B-A3B": "ad44e777bcd18fa416d9da3bd8f70d33ebb85d39", "Qwen/Qwen3.5-27B": "fc05daec18b0a78c049392ed2e771dde82bdf654", + "Qwen/Qwen3.8-27B": "1d4bf0f2ff6012fd82039f2fa52739d0dd7c60c0", "Qwen/Qwen3.5-35B-A3B": "59d61f3ce65a6d9863b86d2e96597125219dc754", "google/gemma-4-31B-it": "842da3794eaa0b77d5f08bae87a17459d91ff475", "google/gemma-4-26B-A4B-it": "4d7ae4984b7db7de8f8457170b3f1a419ee76d52", diff --git a/tests/integration/megatron/model_support/workflow_resources.py b/tests/integration/megatron/model_support/workflow_resources.py index 7b705e1c4..ea1b77280 100644 --- a/tests/integration/megatron/model_support/workflow_resources.py +++ b/tests/integration/megatron/model_support/workflow_resources.py @@ -425,6 +425,9 @@ class HandlerWorkflowResources(BaseModel): (81_700, 76_400, 4_850, 0.88, 2.5), ), } +_B300_THROUGHPUT_FINGERPRINT_OVERRIDES = { + "Qwen/Qwen3.8-27B": "b07ee7ec6338ec021463a43a90fc96c5c5a036b4a04d90b80e1d22c1eef86774", +} _H200_THROUGHPUT_FLOORS = { "llama3_dense": (18_300, 17_200, 4_400, 0.89, 7.0), "qwen3_dense": (24_100, 23_100, 5_000, 0.91, 7.0), @@ -554,7 +557,22 @@ def handler_workflow_resources_for_base_model( base_model, allow_unvalidated_arch=allow_unvalidated_arch, ) - return HANDLER_WORKFLOW_RESOURCES.get(spec.handler_key) + resources = HANDLER_WORKFLOW_RESOURCES.get(spec.handler_key) + fingerprint = _B300_THROUGHPUT_FINGERPRINT_OVERRIDES.get(base_model) + if resources is None or resources.e2e_throughput is None or fingerprint is None: + return resources + stage = resources.e2e_throughput + config = stage.throughput + if config is None: + raise RuntimeError(f"missing throughput config for {base_model}") + thresholds = dict(config.thresholds) + thresholds["b300"] = thresholds["b300"].model_copy( + update={"calibration_fingerprint": fingerprint} + ) + config = config.model_copy(update={"thresholds": thresholds}) + return resources.model_copy( + update={"e2e_throughput": stage.model_copy(update={"throughput": config})} + ) def _h200_equivalent_slots_for_total_gib(total_gib: float) -> int: diff --git a/tests/unit/test_trainer_rank_validation.py b/tests/unit/test_trainer_rank_validation.py index 09d038828..351775267 100644 --- a/tests/unit/test_trainer_rank_validation.py +++ b/tests/unit/test_trainer_rank_validation.py @@ -978,7 +978,13 @@ def test_checkpoint_slot_adapter_config_is_validated_and_copied() -> None: trainer._validate_checkpoint_adapter_config("student", {"r": 8}, alpha=None) -def test_qwen35_checkpoint_adapter_config_captures_attention_dimensions() -> None: +@pytest.mark.parametrize( + "base_model", + ("Qwen/Qwen3.5-4B", "Qwen/Qwen3.6-27B", "Qwen/Qwen3.8-27B"), +) +def test_qwen35_checkpoint_adapter_config_captures_attention_dimensions( + base_model: str, +) -> None: runtime = _runtime() runtime.provider.num_attention_heads = 16 runtime.provider.num_query_groups = 4 @@ -988,7 +994,7 @@ def test_qwen35_checkpoint_adapter_config_captures_attention_dimensions() -> Non retained = trainer._validate_checkpoint_adapter_config( "student", { - "base_model_name_or_path": "Qwen/Qwen3.5-4B", + "base_model_name_or_path": base_model, "r": 8, "lora_alpha": 16, "target_modules": ["q_proj"], diff --git a/uv.lock b/uv.lock index 56a6f586c..dbc942324 100644 --- a/uv.lock +++ b/uv.lock @@ -4535,7 +4535,7 @@ requires-dist = [ { name = "nvidia-nccl-cu13", marker = "sys_platform == 'linux' and extra == 'backend-cu130'", specifier = "==2.28.9" }, { name = "nvidia-resiliency-ext", marker = "sys_platform == 'linux' and extra == 'backend'", specifier = "<0.5" }, { name = "nvidia-resiliency-ext", marker = "sys_platform == 'linux' and extra == 'backend-cu130'", specifier = "<0.5" }, - { name = "openai", specifier = ">=2.14.0" }, + { name = "openai", specifier = ">=2.14.0,<3" }, { name = "peft", marker = "extra == 'backend'", specifier = ">=0.14.0" }, { name = "peft", marker = "extra == 'backend-cu130'", specifier = ">=0.14.0" }, { name = "peft", marker = "extra == 'megatron'", specifier = ">=0.14.0" },