Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 41 additions & 7 deletions scripts/Jenkinsfile
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ pipeline {
)
string(
name: 'WEIGHT_FREE_QAIC_DEVICE_GROUPS',
defaultValue: '0,1,2,3;4,5,6,7;8,9,10,11;12,13,14,15',
defaultValue: '0,1,2,3;4,5,6,7;8,9,10,11',
description: 'Exclusive four-device QAIC groups per weight-free worker, separated by semicolons'
)
}
Expand All @@ -173,7 +173,7 @@ pipeline {
CI_IMPACT_MODE = 'full'
CI_SELECTIVE_LAYER_FILTER = noopMarkerFilter()
CI_QAIC_WORKERS = '3'
DISAGG_QAIC_WORKERS = '4'
DISAGG_QAIC_WORKERS = '3'
MM_COMPILE_N = "${params.MM_COMPILE_WORKERS}"
MM_EXEC_N = qaicDeviceGroupCount(params.MM_QAIC_DEVICE_GROUPS)
WEIGHT_FREE_EXEC_N = qaicDeviceGroupCount(params.WEIGHT_FREE_QAIC_DEVICE_GROUPS)
Expand Down Expand Up @@ -667,15 +667,49 @@ pipeline {
runImpactStage('weight_free_qaic', params.RUN_WEIGHT_FREE_QAIC, params.TEST_PROFILE) {
timeout(time: 360, unit: 'MINUTES') {
sh '''
sudo docker exec -e QEFF_MODEL_TIER=${MODEL_TIER} -e TMPDIR=/efficient-transformers/.ci-tmp/weight_free_qaic -e QEFF_QAIC_DEVICE_GROUPS="${WEIGHT_FREE_QAIC_DEVICE_GROUPS}" -e QEFF_QAIC_DEVICES_PER_WORKER=4 ${BUILD_TAG} bash -c "
sudo docker exec -e QEFF_MODEL_TIER="${MODEL_TIER}" -e TMPDIR=/efficient-transformers/.ci-tmp/weight_free_qaic -e QEFF_QAIC_DEVICE_GROUPS="${WEIGHT_FREE_QAIC_DEVICE_GROUPS}" -e QEFF_QAIC_DEVICES_PER_WORKER=4 -e WEIGHT_FREE_EXEC_N="${WEIGHT_FREE_EXEC_N}" -e IMPACT_TEST_FILTER="${IMPACT_TEST_FILTER}" -e CI_SELECTIVE_LAYER_FILTER="${CI_SELECTIVE_LAYER_FILTER}" -e CI_IMPACT_HEAD="${CI_IMPACT_HEAD}" ${BUILD_TAG} bash -c '
cd /efficient-transformers && . preflight_qeff/bin/activate && pip install -r examples/dynamo/causal_lm/requirements.txt &&
mkdir -p /efficient-transformers/weight_free_qaic && mkdir -p /efficient-transformers/Qeff_logs && export QEFF_LOG_PATH=/efficient-transformers/Qeff_logs &&
export TOKENIZERS_PARALLELISM=false && export QEFF_ISOLATE_QAIC_WORKERS=1 && export QEFF_HOME=/efficient-transformers/weight_free_qaic &&
export HF_HUB_CACHE=/huggingface_hub && export HF_HUB_ENABLE_HF_TRANSFER=1 &&
# Run the disaggregated subset in fresh workers after the main weight-free suite releases its QAIC contexts.
python -m pytest tests/weight_free -m '(on_qaic) and (not disagg_dma) and ${IMPACT_TEST_FILTER} and ${CI_SELECTIVE_LAYER_FILTER}' --dist loadgroup -n ${WEIGHT_FREE_EXEC_N} -p scripts.ci_impact.pytest_plugin --impact-plan=.ci-impact-plan.json --impact-stage=weight_free_qaic --junitxml=tests/tests_log_weight_free_qaic.xml --durations=10 &&
python -m pytest tests/weight_free/test_disaggregated.py -m '(on_qaic) and (disagg_dma) and ${IMPACT_TEST_FILTER} and ${CI_SELECTIVE_LAYER_FILTER}' --dist loadgroup -n ${WEIGHT_FREE_EXEC_N} -p scripts.ci_impact.pytest_plugin --impact-plan=.ci-impact-plan.json --impact-stage=weight_free_qaic --junitxml=tests/tests_log_weight_free_disagg_qaic.xml --durations=10 &&
deactivate"
python -m pytest tests/weight_free -m "(on_qaic) and (not disagg_dma) and (not weight_free_blocking) and $IMPACT_TEST_FILTER and $CI_SELECTIVE_LAYER_FILTER" --dist loadgroup -n "$WEIGHT_FREE_EXEC_N" -p scripts.ci_impact.pytest_plugin --impact-plan=.ci-impact-plan.json --impact-stage=weight_free_qaic --junitxml=tests/tests_log_weight_free_qaic.xml --durations=10 &&
# Run blocking parity configurations serially while retaining QAIC worker isolation.
python -m pytest tests/weight_free/test_on_qaic_causallm.py -m "(on_qaic) and (weight_free_blocking) and (not disagg_dma) and $IMPACT_TEST_FILTER and $CI_SELECTIVE_LAYER_FILTER" --dist loadgroup -n 1 -p scripts.ci_impact.pytest_plugin --impact-plan=.ci-impact-plan.json --impact-stage=weight_free_qaic --junitxml=tests/tests_log_weight_free_blocking_qaic.xml --durations=10 &&
set -o pipefail &&
for disagg_suite in parity blocking; do
if [ "$disagg_suite" = blocking ]; then
disagg_filter="weight_free_blocking"
else
disagg_filter="not weight_free_blocking"
fi &&
collection_status=0 &&
python -m pytest tests/weight_free/test_disaggregated.py -m "(on_qaic) and (disagg_dma) and ($disagg_filter) and $IMPACT_TEST_FILTER and $CI_SELECTIVE_LAYER_FILTER" -o addopts="" --collect-only -q -p scripts.ci_impact.pytest_plugin --impact-plan=.ci-impact-plan.json --impact-stage=weight_free_qaic --impact-head="$CI_IMPACT_HEAD" 2>&1 | tee "Qeff_logs/weight_free_disagg_${disagg_suite}_collection.log" || collection_status=$?
if [ "$collection_status" -ne 0 ] && [ "$collection_status" -ne 5 ]; then
exit "$collection_status"
fi &&
{ grep "^tests/.*::" "Qeff_logs/weight_free_disagg_${disagg_suite}_collection.log" || true; } > ".ci-weight-free-disagg-${disagg_suite}-collected.txt" &&
mapfile -t SELECTED_TESTS < ".ci-weight-free-disagg-${disagg_suite}-collected.txt" &&
if [ "${#SELECTED_TESTS[@]}" -eq 0 ]; then
echo "<?xml version=\"1.0\" encoding=\"utf-8\"?><testsuites></testsuites>" > "tests/tests_log_weight_free_disagg_${disagg_suite}_qaic.xml"
else
rm -f tests/tests_log_weight_free_disagg_${disagg_suite}_batch_*.xml &&
batch=0 &&
exit_status=0 &&
# Keep one test per worker process so QAIC contexts are released between tests.
for ((start=0; start<${#SELECTED_TESTS[@]}; start+=WEIGHT_FREE_EXEC_N)); do
BATCH_TESTS=("${SELECTED_TESTS[@]:start:WEIGHT_FREE_EXEC_N}") &&
batch_workers=${#BATCH_TESTS[@]} &&
echo "Running weight-free disaggregated $disagg_suite batch $batch with $batch_workers fresh workers" &&
python -m pytest "${BATCH_TESTS[@]}" --dist loadgroup -n "$batch_workers" --junitxml="tests/tests_log_weight_free_disagg_${disagg_suite}_batch_${batch}.xml" --durations=10 || exit_status=$? &&
if [ "$exit_status" -ne 0 ]; then break; fi &&
batch=$((batch + 1))
done &&
junitparser merge tests/tests_log_weight_free_disagg_${disagg_suite}_batch_*.xml "tests/tests_log_weight_free_disagg_${disagg_suite}_qaic.xml" &&
if [ "$exit_status" -ne 0 ]; then exit "$exit_status"; fi
fi || exit $?
done &&
deactivate
'
'''
}
}
Expand Down
23 changes: 14 additions & 9 deletions tests/weight_free/_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
from __future__ import annotations

import copy
import random
from collections import Counter
from pathlib import Path

Expand Down Expand Up @@ -130,19 +131,12 @@ def _embedding_test_param(model_type, model_id, pooling):
}

_BLOCKING_SUPPORTED_TYPES = {
"gemma",
"gemma2",
"glm4_moe",
"gpt_oss",
"granite",
"granitemoe",
"llama",
"mistral",
"mixtral",
"qwen2",
"qwen3",
"qwen3_moe",
"starcoder2",
}

WEIGHT_FREE_BLOCKING_CAUSAL_LM_MODEL_IDS = {
Expand All @@ -154,6 +148,18 @@ def _is_weight_free_blocking_qaic_case_supported(model_type: str, blocking_key:
return not (model_type == "mixtral" and blocking_key == "h")


def _weight_free_blocking_qaic_modes(model_type: str) -> list[str]:
modes = [
mode
for mode in WEIGHT_FREE_BLOCKING_MODE_CASES
if _is_weight_free_blocking_qaic_case_supported(model_type, mode)
]
if model_type == "gpt_oss":
return modes
# A fixed per-model seed keeps collection identical across xdist workers.
return random.Random(f"42:{model_type}").sample(modes, k=min(2, len(modes)))


WEIGHT_FREE_BLOCKING_QAIC_CASES = [
pytest.param(
model_type,
Expand All @@ -163,8 +169,7 @@ def _is_weight_free_blocking_qaic_case_supported(model_type: str, blocking_key:
id=f"{model_type}-{blocking_key}",
)
for model_type, model_id in sorted(WEIGHT_FREE_BLOCKING_CAUSAL_LM_MODEL_IDS.items())
for blocking_key in WEIGHT_FREE_BLOCKING_MODE_CASES
if _is_weight_free_blocking_qaic_case_supported(model_type, blocking_key)
for blocking_key in _weight_free_blocking_qaic_modes(model_type)
]

# ---------------------------------------------------------------------------
Expand Down
3 changes: 0 additions & 3 deletions tests/weight_free/test_disaggregated.py
Original file line number Diff line number Diff line change
Expand Up @@ -513,15 +513,13 @@ def _run_disagg_prefill_decode_tokens(
prompt: str,
generation_len: int,
full_batch_size: int,
stages: int,
) -> np.ndarray:
assert generation_len >= 1, "disaggregated compile/artifact token check expects at least one generated token"
sessions = []
try:
prefill_session = QAICInferenceSession(
str(prefill_qpc),
kv_dma_share=True,
stages=stages,
full_batch_size=full_batch_size,
cluster_id="prefill",
)
Expand Down Expand Up @@ -870,7 +868,6 @@ def test_weight_free_decode_blocked_disaggregated_compile_and_artifact_metrics(
DECODE_BLOCKING_PARITY_PROMPT,
DECODE_BLOCKING_PARITY_GENERATION_LEN,
full_batch_size,
DECODE_BLOCKING_PREFILL_STAGES,
)
hf_tokens_for_compare = np.asarray(hf_tokens).flatten()[:DECODE_BLOCKING_PARITY_GENERATION_LEN]
assert np.array_equal(hf_tokens_for_compare, qaic_tokens), (
Expand Down
43 changes: 7 additions & 36 deletions tests/weight_free/test_on_qaic_causallm.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,6 @@

from __future__ import annotations

import os

import numpy as np
import pytest
import torch
Expand Down Expand Up @@ -55,46 +53,17 @@
HEAD_BLOCKING_NUM_DEVICES = 4


def _blocking_runtime_options(blocking_key: str) -> tuple[dict, list[int] | None]:
def _blocking_compile_options(blocking_key: str) -> dict:
if blocking_key in {"h", "hq", "hkv"}:
device_groups = get_qaic_mdp_device_groups(devices_per_group=HEAD_BLOCKING_NUM_DEVICES)
if not device_groups:
pytest.skip(f"No available QAIC MDP device group with {HEAD_BLOCKING_NUM_DEVICES} devices")
return {"num_devices": HEAD_BLOCKING_NUM_DEVICES, "user_tiled": True}, device_groups[0]
return {"num_devices": HEAD_BLOCKING_NUM_DEVICES, "user_tiled": True}

device_ids = get_available_device_id()
if device_ids is None:
pytest.skip("No available QAIC device")
return {}, device_ids


def _runtime_device_ids(device_ids: list[int]) -> list[int]:
if "QAIC_VISIBLE_DEVICES" in os.environ:
return list(range(len(device_ids)))
return device_ids


@pytest.mark.cpu_only
@pytest.mark.parametrize(
"visible_devices,discovered_device_ids,expected_device_ids",
[
(None, [4, 5, 6, 7], [4, 5, 6, 7]),
("4,5,6,7", [4, 5, 6, 7], [0, 1, 2, 3]),
("4,5,6,7", [4], [0]),
],
)
def test_runtime_device_ids_respect_qaic_visibility(
monkeypatch,
visible_devices,
discovered_device_ids,
expected_device_ids,
):
if visible_devices is None:
monkeypatch.delenv("QAIC_VISIBLE_DEVICES", raising=False)
else:
monkeypatch.setenv("QAIC_VISIBLE_DEVICES", visible_devices)

assert _runtime_device_ids(discovered_device_ids) == expected_device_ids
return {}


@pytest.mark.weight_free
Expand Down Expand Up @@ -347,6 +316,9 @@ def test_weight_free_blocked_hw_hf_parity_and_artifact_metrics(
tmp_export_dir,
):
"""HF PT tokens match blocked weight-free QAIC tokens."""
if model_type == "gpt_oss" and blocking_key == "hq":
pytest.xfail("tokens not matching - to fix")

from QEfficient.utils.run_utils import ApiRunner

use_onnx_subfunctions = True
Expand Down Expand Up @@ -396,7 +368,7 @@ def test_weight_free_blocked_hw_hf_parity_and_artifact_metrics(
assert_has_subfunctions(onnx_path, qeff_model)
assert_blocked_kv_ops_for_mode(onnx_path, qeff_model, blocking_key)

compile_options, device_ids = _blocking_runtime_options(blocking_key)
compile_options = _blocking_compile_options(blocking_key)
qeff_model.compile(
onnx_path=str(onnx_path),
compile_dir=str(tmp_export_dir / f"wf_blocking_{model_type}_{blocking_key}_{use_onnx_subfunctions}_compile"),
Expand All @@ -412,7 +384,6 @@ def test_weight_free_blocked_hw_hf_parity_and_artifact_metrics(
qaic_output = qeff_model.generate(
tokenizer=tokenizer,
prompts=prompts,
device_ids=_runtime_device_ids(device_ids),
)
assert qaic_output is not None, "Blocked weight-free QAIC generate returned None"
assert hasattr(qaic_output, "generated_ids") and qaic_output.generated_ids is not None
Expand Down
Loading