Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 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
3 changes: 3 additions & 0 deletions codeflash/api/aiservice.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from pydantic.json import pydantic_encoder

from codeflash.cli_cmds.console import console, logger
from codeflash.code_utils.config_consts import get_n_candidates, get_n_candidates_lp
from codeflash.code_utils.env_utils import get_codeflash_api_key
from codeflash.code_utils.git_utils import get_last_commit_author_if_pr_exists, get_repo_owner_and_name
from codeflash.lsp.helpers import is_LSP_enabled
Expand Down Expand Up @@ -131,6 +132,7 @@ def optimize_python_code( # noqa: D417
"current_username": get_last_commit_author_if_pr_exists(None),
"repo_owner": git_repo_owner,
"repo_name": git_repo_name,
"n_candidates": get_n_candidates(),
}

logger.info("Generating optimized candidates…")
Expand Down Expand Up @@ -192,6 +194,7 @@ def optimize_python_code_line_profiler( # noqa: D417
"experiment_metadata": experiment_metadata,
"codeflash_version": codeflash_version,
"lsp_mode": is_LSP_enabled(),
"n_candidates_lp": get_n_candidates_lp(),
}

logger.info("Generating optimized candidates…")
Expand Down
31 changes: 31 additions & 0 deletions codeflash/code_utils/config_consts.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,34 @@
MIN_TESTCASE_PASSED_THRESHOLD = 6
REPEAT_OPTIMIZATION_PROBABILITY = 0.1
DEFAULT_IMPORTANCE_THRESHOLD = 0.001
N_CANDIDATES_LP = 6

# LSP-specific
N_CANDIDATES_LSP = 3
N_TESTS_TO_GENERATE_LSP = 2
TOTAL_LOOPING_TIME_LSP = 10.0 # Kept same timing for LSP mode to avoid in increase in performance reporting
N_CANDIDATES_LP_LSP = 3


def get_n_candidates() -> int:
from codeflash.lsp.helpers import is_LSP_enabled

return N_CANDIDATES_LSP if is_LSP_enabled() else N_CANDIDATES


def get_n_candidates_lp() -> int:
from codeflash.lsp.helpers import is_LSP_enabled

return N_CANDIDATES_LP_LSP if is_LSP_enabled() else N_CANDIDATES_LP


def get_n_tests_to_generate() -> int:
from codeflash.lsp.helpers import is_LSP_enabled

return N_TESTS_TO_GENERATE_LSP if is_LSP_enabled() else N_TESTS_TO_GENERATE


def get_total_looping_time() -> float:
from codeflash.lsp.helpers import is_LSP_enabled

return TOTAL_LOOPING_TIME_LSP if is_LSP_enabled() else TOTAL_LOOPING_TIME
4 changes: 2 additions & 2 deletions codeflash/code_utils/git_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
from unidiff import PatchSet

from codeflash.cli_cmds.console import logger
from codeflash.code_utils.config_consts import N_CANDIDATES
from codeflash.code_utils.config_consts import get_n_candidates

if TYPE_CHECKING:
from git import Repo
Expand Down Expand Up @@ -164,7 +164,7 @@ def create_git_worktrees(
) -> tuple[Path | None, list[Path]]:
if git_root and worktree_root_dir:
worktree_root = Path(tempfile.mkdtemp(dir=worktree_root_dir))
worktrees = [Path(tempfile.mkdtemp(dir=worktree_root)) for _ in range(N_CANDIDATES + 1)]
worktrees = [Path(tempfile.mkdtemp(dir=worktree_root)) for _ in range(get_n_candidates() + 1)]
for worktree in worktrees:
subprocess.run(["git", "worktree", "add", "-d", worktree], cwd=module_root, check=True)
else:
Expand Down
50 changes: 30 additions & 20 deletions codeflash/optimization/function_optimizer.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,11 @@
)
from codeflash.code_utils.config_consts import (
INDIVIDUAL_TESTCASE_TIMEOUT,
N_CANDIDATES,
N_TESTS_TO_GENERATE,
REPEAT_OPTIMIZATION_PROBABILITY,
TOTAL_LOOPING_TIME,
get_n_candidates,
get_n_candidates_lp,
get_n_tests_to_generate,
get_total_looping_time,
)
from codeflash.code_utils.deduplicate_code import normalize_code
from codeflash.code_utils.edit_generated_tests import (
Expand Down Expand Up @@ -231,8 +232,9 @@ def __init__(
self.generate_and_instrument_tests_results: (
tuple[GeneratedTestsList, dict[str, set[FunctionCalledInTest]], OptimizationSet] | None
) = None
n_tests = get_n_tests_to_generate()
self.executor = concurrent.futures.ThreadPoolExecutor(
max_workers=N_TESTS_TO_GENERATE + 2 if self.experiment_id is None else N_TESTS_TO_GENERATE + 3
max_workers=n_tests + 2 if self.experiment_id is None else n_tests + 3
)

def can_be_optimized(self) -> Result[tuple[bool, CodeOptimizationContext, dict[Path, str]], str]:
Expand Down Expand Up @@ -282,17 +284,18 @@ def generate_and_instrument_tests(
]
]:
"""Generate and instrument tests, returning all necessary data for optimization."""
n_tests = get_n_tests_to_generate()
generated_test_paths = [
get_test_file_path(
self.test_cfg.tests_root, self.function_to_optimize.function_name, test_index, test_type="unit"
)
for test_index in range(N_TESTS_TO_GENERATE)
for test_index in range(n_tests)
]
generated_perf_test_paths = [
get_test_file_path(
self.test_cfg.tests_root, self.function_to_optimize.function_name, test_index, test_type="perf"
)
for test_index in range(N_TESTS_TO_GENERATE)
for test_index in range(n_tests)
]

with progress_bar(
Expand Down Expand Up @@ -474,7 +477,7 @@ def determine_best_candidate(
dependency_code=code_context.read_only_context_code,
trace_id=self.function_trace_id[:-4] + exp_type if self.experiment_id else self.function_trace_id,
line_profiler_results=original_code_baseline.line_profile_results["str_out"],
num_candidates=10,
num_candidates=get_n_candidates_lp(),
experiment_metadata=ExperimentMetadata(
id=self.experiment_id, group="control" if exp_type == "EXP0" else "experiment"
)
Expand Down Expand Up @@ -1010,7 +1013,8 @@ def generate_tests_and_optimizations(
generated_perf_test_paths: list[Path],
run_experiment: bool = False, # noqa: FBT001, FBT002
) -> Result[tuple[GeneratedTestsList, dict[str, set[FunctionCalledInTest]], OptimizationSet], str]:
assert len(generated_test_paths) == N_TESTS_TO_GENERATE
n_tests = get_n_tests_to_generate()
assert len(generated_test_paths) == n_tests
console.rule()
# Submit the test generation task as future
future_tests = self.submit_test_generation_tasks(
Expand All @@ -1020,12 +1024,13 @@ def generate_tests_and_optimizations(
generated_test_paths,
generated_perf_test_paths,
)
n_candidates = get_n_candidates()
future_optimization_candidates = self.executor.submit(
self.aiservice_client.optimize_python_code,
read_writable_code.markdown,
read_only_context_code,
self.function_trace_id[:-4] + "EXP0" if run_experiment else self.function_trace_id,
N_CANDIDATES,
n_candidates,
ExperimentMetadata(id=self.experiment_id, group="control") if run_experiment else None,
)
future_candidates_exp = None
Expand All @@ -1040,7 +1045,7 @@ def generate_tests_and_optimizations(
read_writable_code.markdown,
read_only_context_code,
self.function_trace_id[:-4] + "EXP1",
N_CANDIDATES,
n_candidates,
ExperimentMetadata(id=self.experiment_id, group="experiment"),
)
futures.append(future_candidates_exp)
Expand Down Expand Up @@ -1416,12 +1421,13 @@ def establish_original_code_baseline(
instrument_codeflash_capture(
self.function_to_optimize, file_path_to_helper_classes, self.test_cfg.tests_root
)
total_looping_time = get_total_looping_time()
behavioral_results, coverage_results = self.run_and_parse_tests(
testing_type=TestingMode.BEHAVIOR,
test_env=test_env,
test_files=self.test_files,
optimization_iteration=0,
testing_time=TOTAL_LOOPING_TIME,
testing_time=total_looping_time,
enable_coverage=test_framework == "pytest",
code_context=code_context,
)
Expand All @@ -1448,15 +1454,15 @@ def establish_original_code_baseline(
test_env=test_env,
test_files=self.test_files,
optimization_iteration=0,
testing_time=TOTAL_LOOPING_TIME,
testing_time=total_looping_time,
enable_coverage=False,
code_context=code_context,
)
else:
benchmarking_results = TestResults()
start_time: float = time.time()
for i in range(100):
if i >= 5 and time.time() - start_time >= TOTAL_LOOPING_TIME * 1.5:
if i >= 5 and time.time() - start_time >= total_looping_time * 1.5:
# * 1.5 to give unittest a bit more time to run
break
test_env["CODEFLASH_LOOP_INDEX"] = str(i + 1)
Expand All @@ -1465,7 +1471,7 @@ def establish_original_code_baseline(
test_env=test_env,
test_files=self.test_files,
optimization_iteration=0,
testing_time=TOTAL_LOOPING_TIME,
testing_time=total_looping_time,
enable_coverage=False,
code_context=code_context,
unittest_loop_index=i + 1,
Expand Down Expand Up @@ -1555,12 +1561,13 @@ def run_optimized_candidate(
self.function_to_optimize, file_path_to_helper_classes, self.test_cfg.tests_root
)

total_looping_time = get_total_looping_time()
candidate_behavior_results, _ = self.run_and_parse_tests(
testing_type=TestingMode.BEHAVIOR,
test_env=test_env,
test_files=self.test_files,
optimization_iteration=optimization_candidate_index,
testing_time=TOTAL_LOOPING_TIME,
testing_time=total_looping_time,
enable_coverage=False,
)
# Remove instrumentation
Expand Down Expand Up @@ -1589,7 +1596,7 @@ def run_optimized_candidate(
test_env=test_env,
test_files=self.test_files,
optimization_iteration=optimization_candidate_index,
testing_time=TOTAL_LOOPING_TIME,
testing_time=total_looping_time,
enable_coverage=False,
)
loop_count = (
Expand All @@ -1607,7 +1614,7 @@ def run_optimized_candidate(
start_time: float = time.time()
loop_count = 0
for i in range(100):
if i >= 5 and time.time() - start_time >= TOTAL_LOOPING_TIME * 1.5:
if i >= 5 and time.time() - start_time >= get_total_looping_time() * 1.5:
# * 1.5 to give unittest a bit more time to run
break
test_env["CODEFLASH_LOOP_INDEX"] = str(i + 1)
Expand All @@ -1616,7 +1623,7 @@ def run_optimized_candidate(
test_env=test_env,
test_files=self.test_files,
optimization_iteration=optimization_candidate_index,
testing_time=TOTAL_LOOPING_TIME,
testing_time=get_total_looping_time(),
unittest_loop_index=i + 1,
)
loop_count = i + 1
Expand Down Expand Up @@ -1655,7 +1662,7 @@ def run_and_parse_tests(
test_env: dict[str, str],
test_files: TestFiles,
optimization_iteration: int,
testing_time: float = TOTAL_LOOPING_TIME,
testing_time: float = get_total_looping_time(),
*,
enable_coverage: bool = False,
pytest_min_loops: int = 5,
Expand Down Expand Up @@ -1794,6 +1801,9 @@ def line_profiler_step(
self, code_context: CodeOptimizationContext, original_helper_code: dict[Path, str], candidate_index: int
) -> dict:
try:
logger.info("Running line profiling to identify performance bottlenecks…")
console.rule()

test_env = self.get_test_env(
codeflash_loop_index=0, codeflash_test_iteration=candidate_index, codeflash_tracer_disable=1
)
Expand All @@ -1803,7 +1813,7 @@ def line_profiler_step(
test_env=test_env,
test_files=self.test_files,
optimization_iteration=0,
testing_time=TOTAL_LOOPING_TIME,
testing_time=get_total_looping_time(),
enable_coverage=False,
code_context=code_context,
line_profiler_output_file=line_profiler_output_file,
Expand Down
8 changes: 4 additions & 4 deletions codeflash/verification/test_runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from codeflash.cli_cmds.console import logger
from codeflash.code_utils.code_utils import custom_addopts, get_run_tmp_file
from codeflash.code_utils.compat import IS_POSIX, SAFE_SYS_EXECUTABLE
from codeflash.code_utils.config_consts import TOTAL_LOOPING_TIME
from codeflash.code_utils.config_consts import get_total_looping_time
from codeflash.code_utils.coverage_utils import prepare_coverage_files
from codeflash.models.models import TestFiles, TestType

Expand Down Expand Up @@ -37,7 +37,7 @@ def run_behavioral_tests(
pytest_timeout: int | None = None,
pytest_cmd: str = "pytest",
verbose: bool = False,
pytest_target_runtime_seconds: int = TOTAL_LOOPING_TIME,
pytest_target_runtime_seconds: int = get_total_looping_time(),
enable_coverage: bool = False,
) -> tuple[Path, subprocess.CompletedProcess, Path | None, Path | None]:
if test_framework == "pytest":
Expand Down Expand Up @@ -151,7 +151,7 @@ def run_line_profile_tests(
cwd: Path,
test_framework: str,
*,
pytest_target_runtime_seconds: float = TOTAL_LOOPING_TIME,
pytest_target_runtime_seconds: float = get_total_looping_time(),
verbose: bool = False,
pytest_timeout: int | None = None,
pytest_min_loops: int = 5, # noqa: ARG001
Expand Down Expand Up @@ -237,7 +237,7 @@ def run_benchmarking_tests(
cwd: Path,
test_framework: str,
*,
pytest_target_runtime_seconds: float = TOTAL_LOOPING_TIME,
pytest_target_runtime_seconds: float = get_total_looping_time(),
verbose: bool = False,
pytest_timeout: int | None = None,
pytest_min_loops: int = 5,
Expand Down