codebuff-agent 2.1.74__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- codebuff_agent-2.1.74.data/data/etc/jupyter/jupyter_server_config.d/codebuff_agent.json +7 -0
- codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/package.json +157 -0
- codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/104.2b8f20f9d1ac2c9db01b.js +1 -0
- codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/150.39fa98117cc8ca590465.js +1 -0
- codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/166.386ff9b2c37ac865fdee.js +1 -0
- codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/216.25cf81131bf7274dabf4.js +1 -0
- codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/353.581bd1cb00746399b62e.js +1 -0
- codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/367.575f9621b63c9a641aa9.js +1 -0
- codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/421.11dac223a2c2da1b09f5.js +1 -0
- codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/432.8513b4c6cc1d1232d165.js +2 -0
- codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/432.8513b4c6cc1d1232d165.js.LICENSE.txt +19 -0
- codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/439.ba5b5ef70ec6de214b8c.js +2 -0
- codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/439.ba5b5ef70ec6de214b8c.js.LICENSE.txt +8 -0
- codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/481.cc2dec9fa869d66f1407.js +1 -0
- codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/487.0796e931d6e651482c14.js +1 -0
- codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/49.d326f554b6f9ccde6580.js +1 -0
- codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/541.26897ea32845659e36be.js +1 -0
- codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/579.e569d3edd8391fd8fbb3.js +1 -0
- codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/58.eb7cc679f7535cc8a2f1.js +2 -0
- codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/58.eb7cc679f7535cc8a2f1.js.LICENSE.txt +8 -0
- codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/666.d9bfdb23f98489d0a188.js +1 -0
- codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/677.d266c39dbed33bc388b3.js +2 -0
- codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/677.d266c39dbed33bc388b3.js.LICENSE.txt +8 -0
- codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/68.26b6f59c8454adb17f20.js +204 -0
- codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/694.4ac443814260057e68f7.js +1 -0
- codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/791.61affb8d3480d3d60e0d.js +1 -0
- codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/848.4d2db09cfbb09374b299.js +2 -0
- codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/848.4d2db09cfbb09374b299.js.LICENSE.txt +9 -0
- codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/868.5cfe7e1a348ec5af1b8c.js +1 -0
- codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/97.ea559a5b8a9a09f17c6b.js +1 -0
- codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/remoteEntry.5292fc9c6df7e7920cc8.js +1 -0
- codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/style.js +4 -0
- codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/third-party-licenses.json +724 -0
- codebuff_agent-2.1.74.dist-info/METADATA +142 -0
- codebuff_agent-2.1.74.dist-info/RECORD +217 -0
- codebuff_agent-2.1.74.dist-info/WHEEL +4 -0
- codebuff_agent-2.1.74.dist-info/licenses/LICENSE +21 -0
- codebuff_core/__init__.py +153 -0
- codebuff_core/codebuff_config_defaults.json +64 -0
- codebuff_core/core/__init__.py +35 -0
- codebuff_core/core/code_validator.py +1238 -0
- codebuff_core/core/error_classifier.py +579 -0
- codebuff_core/core/exceptions.py +49 -0
- codebuff_core/data/tiktoken_cache/9b5ad71b2ce5302211f9c61530b329a4922fc6a4 +100256 -0
- codebuff_core/directory_policy.py +32 -0
- codebuff_core/factory.py +155 -0
- codebuff_core/interfaces.py +197 -0
- codebuff_core/knowledge/__init__.py +31 -0
- codebuff_core/knowledge/chunking.py +375 -0
- codebuff_core/knowledge/loader.py +339 -0
- codebuff_core/knowledge/watchdog_service.py +354 -0
- codebuff_core/legacy_paths.py +69 -0
- codebuff_core/llm/__init__.py +13 -0
- codebuff_core/llm/service.py +407 -0
- codebuff_core/managers/__init__.py +22 -0
- codebuff_core/managers/config_manager.py +690 -0
- codebuff_core/managers/remote_config.py +673 -0
- codebuff_core/managers/remote_skills.py +539 -0
- codebuff_core/managers/session_manager.py +255 -0
- codebuff_core/middleware/__init__.py +3 -0
- codebuff_core/middleware/error_recovery.py +394 -0
- codebuff_core/middleware/logging_utils.py +33 -0
- codebuff_core/models/__init__.py +111 -0
- codebuff_core/models/agent.py +306 -0
- codebuff_core/models/chat.py +57 -0
- codebuff_core/models/common.py +82 -0
- codebuff_core/models/rag.py +300 -0
- codebuff_core/platform_rules.py +127 -0
- codebuff_core/prompts/__init__.py +44 -0
- codebuff_core/prompts/auto_agent_prompts.py +1278 -0
- codebuff_core/services/__init__.py +30 -0
- codebuff_core/services/agent_service.py +345 -0
- codebuff_core/services/analytics_aggregate.py +837 -0
- codebuff_core/services/chat_service.py +898 -0
- codebuff_core/services/context_condenser.py +306 -0
- codebuff_core/services/pii_guardrail.py +393 -0
- codebuff_core/services/rag_chat_agent.py +2993 -0
- codebuff_core/services/rag_collections.py +2740 -0
- codebuff_core/services/rag_manager.py +890 -0
- codebuff_core/services/rag_service.py +145 -0
- codebuff_core/services/retriever.py +260 -0
- codebuff_core/services/vllm_embedding_service.py +290 -0
- codebuff_core/tests/__init__.py +1 -0
- codebuff_core/tests/conftest.py +81 -0
- codebuff_core/tests/test_error_recovery.py +647 -0
- codebuff_core/tests/test_factory.py +135 -0
- codebuff_core/tests/test_services.py +150 -0
- jupyter_ext/__init__.py +266 -0
- jupyter_ext/_version.py +4 -0
- jupyter_ext/agent/__init__.py +49 -0
- jupyter_ext/agent/agent_factory.py +2270 -0
- jupyter_ext/agent/ast_tools.py +451 -0
- jupyter_ext/agent/autofix_middleware.py +481 -0
- jupyter_ext/agent/background_procs.py +372 -0
- jupyter_ext/agent/code_history_middleware.py +211 -0
- jupyter_ext/agent/codex_autofix.py +160 -0
- jupyter_ext/agent/codex_bridge.py +3339 -0
- jupyter_ext/agent/codex_hooks.py +320 -0
- jupyter_ext/agent/codex_slash.py +685 -0
- jupyter_ext/agent/codex_threads.py +114 -0
- jupyter_ext/agent/content_injection.py +377 -0
- jupyter_ext/agent/continuation_middleware.py +548 -0
- jupyter_ext/agent/custom_skills.py +335 -0
- jupyter_ext/agent/file_diffs.py +99 -0
- jupyter_ext/agent/git_history.py +188 -0
- jupyter_ext/agent/guardrail_middleware.py +421 -0
- jupyter_ext/agent/json_repair_utils.py +54 -0
- jupyter_ext/agent/langchain_tools.py +2673 -0
- jupyter_ext/agent/mermaid_fixer.py +289 -0
- jupyter_ext/agent/mermaid_middleware.py +135 -0
- jupyter_ext/agent/middleware.py +359 -0
- jupyter_ext/agent/middleware_prompts.py +53 -0
- jupyter_ext/agent/observability.py +334 -0
- jupyter_ext/agent/patch_tool_calls.py +57 -0
- jupyter_ext/agent/prompts.py +112 -0
- jupyter_ext/agent/skill_middleware.py +257 -0
- jupyter_ext/agent/skills/airflow_dag_development.md +36 -0
- jupyter_ext/agent/skills/anomaly_detection.md +12 -0
- jupyter_ext/agent/skills/athena_sql.md +156 -0
- jupyter_ext/agent/skills/bdp_table_search.md +33 -0
- jupyter_ext/agent/skills/bigquery.md +65 -0
- jupyter_ext/agent/skills/bigquery_oracle.md +231 -0
- jupyter_ext/agent/skills/bigquery_tuning.md +307 -0
- jupyter_ext/agent/skills/classification_evaluation.md +11 -0
- jupyter_ext/agent/skills/clustering_analysis.md +12 -0
- jupyter_ext/agent/skills/comprehensive_eda.md +11 -0
- jupyter_ext/agent/skills/data_analysis.md +236 -0
- jupyter_ext/agent/skills/data_cleaning_pipeline.md +12 -0
- jupyter_ext/agent/skills/data_loading.md +158 -0
- jupyter_ext/agent/skills/data_quality_report.md +13 -0
- jupyter_ext/agent/skills/dimensionality_reduction.md +12 -0
- jupyter_ext/agent/skills/emr_script_development.md +17 -0
- jupyter_ext/agent/skills/experiment_tracking.md +13 -0
- jupyter_ext/agent/skills/feature_importance_analysis.md +10 -0
- jupyter_ext/agent/skills/hyperparameter_tuning.md +12 -0
- jupyter_ext/agent/skills/hypothesis_testing.md +12 -0
- jupyter_ext/agent/skills/imbalanced_data_handling.md +12 -0
- jupyter_ext/agent/skills/inference.md +392 -0
- jupyter_ext/agent/skills/lambda_function_development.md +19 -0
- jupyter_ext/agent/skills/model_comparison.md +11 -0
- jupyter_ext/agent/skills/model_explainability.md +12 -0
- jupyter_ext/agent/skills/model_training.md +318 -0
- jupyter_ext/agent/skills/pyspark.md +352 -0
- jupyter_ext/agent/skills/python_code_quality.md +35 -0
- jupyter_ext/agent/skills/regression_evaluation.md +12 -0
- jupyter_ext/agent/skills/spring_java_development.md +35 -0
- jupyter_ext/agent/skills/text_preprocessing.md +12 -0
- jupyter_ext/agent/skills/time_series_analysis.md +13 -0
- jupyter_ext/agent/skills/unit_testing.md +203 -0
- jupyter_ext/agent/todo_prompts.py +57 -0
- jupyter_ext/agent/tool_report.py +45 -0
- jupyter_ext/analytics_ingest.py +599 -0
- jupyter_ext/etc/jupyter/jupyter_server_config.d/codebuff_agent.json +7 -0
- jupyter_ext/handlers/__init__.py +8 -0
- jupyter_ext/handlers/analytics_handler.py +170 -0
- jupyter_ext/handlers/benchmark_handler.py +794 -0
- jupyter_ext/handlers/eda_benchmark_handler.py +1328 -0
- jupyter_ext/handlers/rag_admin_handler.py +689 -0
- jupyter_ext/handlers/websocket.py +497 -0
- jupyter_ext/http_handlers.py +8744 -0
- jupyter_ext/idle_monitor.py +346 -0
- jupyter_ext/labextension/package.json +157 -0
- jupyter_ext/labextension/static/104.2b8f20f9d1ac2c9db01b.js +1 -0
- jupyter_ext/labextension/static/150.39fa98117cc8ca590465.js +1 -0
- jupyter_ext/labextension/static/166.386ff9b2c37ac865fdee.js +1 -0
- jupyter_ext/labextension/static/216.25cf81131bf7274dabf4.js +1 -0
- jupyter_ext/labextension/static/353.581bd1cb00746399b62e.js +1 -0
- jupyter_ext/labextension/static/367.575f9621b63c9a641aa9.js +1 -0
- jupyter_ext/labextension/static/421.11dac223a2c2da1b09f5.js +1 -0
- jupyter_ext/labextension/static/432.8513b4c6cc1d1232d165.js +2 -0
- jupyter_ext/labextension/static/432.8513b4c6cc1d1232d165.js.LICENSE.txt +19 -0
- jupyter_ext/labextension/static/439.ba5b5ef70ec6de214b8c.js +2 -0
- jupyter_ext/labextension/static/439.ba5b5ef70ec6de214b8c.js.LICENSE.txt +8 -0
- jupyter_ext/labextension/static/481.cc2dec9fa869d66f1407.js +1 -0
- jupyter_ext/labextension/static/487.0796e931d6e651482c14.js +1 -0
- jupyter_ext/labextension/static/49.d326f554b6f9ccde6580.js +1 -0
- jupyter_ext/labextension/static/541.26897ea32845659e36be.js +1 -0
- jupyter_ext/labextension/static/579.e569d3edd8391fd8fbb3.js +1 -0
- jupyter_ext/labextension/static/58.eb7cc679f7535cc8a2f1.js +2 -0
- jupyter_ext/labextension/static/58.eb7cc679f7535cc8a2f1.js.LICENSE.txt +8 -0
- jupyter_ext/labextension/static/666.d9bfdb23f98489d0a188.js +1 -0
- jupyter_ext/labextension/static/677.d266c39dbed33bc388b3.js +2 -0
- jupyter_ext/labextension/static/677.d266c39dbed33bc388b3.js.LICENSE.txt +8 -0
- jupyter_ext/labextension/static/68.26b6f59c8454adb17f20.js +204 -0
- jupyter_ext/labextension/static/694.4ac443814260057e68f7.js +1 -0
- jupyter_ext/labextension/static/791.61affb8d3480d3d60e0d.js +1 -0
- jupyter_ext/labextension/static/848.4d2db09cfbb09374b299.js +2 -0
- jupyter_ext/labextension/static/848.4d2db09cfbb09374b299.js.LICENSE.txt +9 -0
- jupyter_ext/labextension/static/868.5cfe7e1a348ec5af1b8c.js +1 -0
- jupyter_ext/labextension/static/97.ea559a5b8a9a09f17c6b.js +1 -0
- jupyter_ext/labextension/static/remoteEntry.5292fc9c6df7e7920cc8.js +1 -0
- jupyter_ext/labextension/static/style.js +4 -0
- jupyter_ext/labextension/static/third-party-licenses.json +724 -0
- jupyter_ext/log_paths.py +113 -0
- jupyter_ext/resource_usage.py +519 -0
- jupyter_ext/secrets_bootstrap.py +116 -0
- jupyter_ext/team_access.py +105 -0
- jupyter_ext/tests/__init__.py +1 -0
- jupyter_ext/tests/test_context.py +234 -0
- jupyter_ext/tests/test_factory.py +563 -0
- jupyter_ext/tests/test_file_finder.py +139 -0
- jupyter_ext/tests/test_handlers.py +58 -0
- jupyter_ext/tests/test_mcp_selftest.py +204 -0
- jupyter_ext/tests/test_mermaid_fixer.py +272 -0
- jupyter_ext/tests/test_resource_tiers.py +298 -0
- jupyter_ext/tools/__init__.py +15 -0
- jupyter_ext/tools/base.py +90 -0
- jupyter_ext/tools/context.py +268 -0
- jupyter_ext/tools/dir_complete.py +86 -0
- jupyter_ext/tools/file_finder.py +303 -0
- jupyter_ext/tools/file_tools.py +244 -0
- jupyter_ext/tools/jupyter_tools.py +296 -0
- jupyter_ext/tools/registry.py +159 -0
- jupyter_ext/tools/search_tools.py +230 -0
- jupyter_ext/tools/security_tools.py +657 -0
- jupyter_ext/tools/shell_tools.py +109 -0
- jupyter_ext/tools/user_tools.py +132 -0
jupyter_ext/log_paths.py
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
"""Centralized log path resolution.
|
|
2
|
+
|
|
3
|
+
All log file names include project code and user ID for traceability.
|
|
4
|
+
Environment variables:
|
|
5
|
+
- HDSP_PRJ_ID: project code
|
|
6
|
+
- JUPYTERHUB_USER / HDSP_BDP_ID: user identifier
|
|
7
|
+
- HDSP_USER_TEAM: team name (for idle shutdown logs)
|
|
8
|
+
- ANALYTICS_LOG_DIR: override for analytics log directory
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import datetime
|
|
14
|
+
import json
|
|
15
|
+
import logging
|
|
16
|
+
import os
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
|
|
19
|
+
from .team_access import resolve_team
|
|
20
|
+
|
|
21
|
+
logger = logging.getLogger(__name__)
|
|
22
|
+
|
|
23
|
+
_PROJECT = os.environ.get("HDSP_PRJ_ID", "unknown")
|
|
24
|
+
_USER = os.environ.get("JUPYTERHUB_USER", "") or os.environ.get(
|
|
25
|
+
"HDSP_BDP_ID", "unknown"
|
|
26
|
+
)
|
|
27
|
+
_TEAM = resolve_team()
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def get_log_dir() -> Path:
|
|
31
|
+
"""Return the log directory, creating it if necessary.
|
|
32
|
+
|
|
33
|
+
Resolution order:
|
|
34
|
+
1. ANALYTICS_LOG_DIR env var
|
|
35
|
+
2. /home/sagemaker-user/.action_logs (SageMaker โ auto-created)
|
|
36
|
+
3. <project_root>/logs (extensions/jupyter/jupyter_ext -> parents[3])
|
|
37
|
+
"""
|
|
38
|
+
env = os.environ.get("ANALYTICS_LOG_DIR")
|
|
39
|
+
if env:
|
|
40
|
+
d = Path(env)
|
|
41
|
+
else:
|
|
42
|
+
sagemaker = Path("/home/sagemaker-user")
|
|
43
|
+
if sagemaker.is_dir():
|
|
44
|
+
d = sagemaker / ".action_logs"
|
|
45
|
+
else:
|
|
46
|
+
d = Path(__file__).resolve().parents[3] / ".action_logs"
|
|
47
|
+
try:
|
|
48
|
+
d.mkdir(parents=True, exist_ok=True)
|
|
49
|
+
return d
|
|
50
|
+
except OSError:
|
|
51
|
+
logger.warning("Failed to create log directory: %s", d)
|
|
52
|
+
# Local fallback
|
|
53
|
+
fallback = Path(__file__).resolve().parents[3] / ".action_logs"
|
|
54
|
+
try:
|
|
55
|
+
fallback.mkdir(parents=True, exist_ok=True)
|
|
56
|
+
return fallback
|
|
57
|
+
except OSError:
|
|
58
|
+
logger.warning("Log directory unavailable: %s", fallback)
|
|
59
|
+
return fallback
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
_LOG_DIR = get_log_dir()
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def get_debug_log_path() -> str:
|
|
66
|
+
"""Return path for agent_debug_{project}_{user}.log."""
|
|
67
|
+
return str(_LOG_DIR / f"agent_debug_{_PROJECT}_{_USER}.log")
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def get_structured_log_path() -> Path:
|
|
71
|
+
"""Return path for agent_structured_{project}_{user}.jsonl."""
|
|
72
|
+
return _LOG_DIR / f"agent_structured_{_PROJECT}_{_USER}.jsonl"
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def get_analytics_log_path(event_type: str, date_str: str) -> Path:
|
|
76
|
+
"""Return path for analytics_{type}_{project}_{user}_{YYYYMMDD}.jsonl."""
|
|
77
|
+
return _LOG_DIR / f"analytics_{event_type}_{_PROJECT}_{_USER}_{date_str}.jsonl"
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def write_idle_shutdown_log(
|
|
81
|
+
idle_seconds: float,
|
|
82
|
+
timeout_setting: int,
|
|
83
|
+
trigger_source: str,
|
|
84
|
+
) -> None:
|
|
85
|
+
"""Write idle shutdown event to a JSONL file.
|
|
86
|
+
|
|
87
|
+
File: idle_shutdown_{project}_{user}_{YYYYMMDD_HHMMSS}.jsonl
|
|
88
|
+
"""
|
|
89
|
+
now = datetime.datetime.now()
|
|
90
|
+
ts_file = now.strftime("%Y%m%d_%H%M%S")
|
|
91
|
+
filename = f"idle_shutdown_{_PROJECT}_{_USER}_{ts_file}.jsonl"
|
|
92
|
+
path = _LOG_DIR / filename
|
|
93
|
+
event = {
|
|
94
|
+
"event": "idle_shutdown",
|
|
95
|
+
"ts": now.isoformat(timespec="seconds"),
|
|
96
|
+
"project": _PROJECT,
|
|
97
|
+
"user_id": _USER,
|
|
98
|
+
"team": _TEAM,
|
|
99
|
+
"idle_seconds": round(idle_seconds, 1),
|
|
100
|
+
"timeout_setting": timeout_setting,
|
|
101
|
+
"trigger_source": trigger_source,
|
|
102
|
+
}
|
|
103
|
+
try:
|
|
104
|
+
with open(path, "a", encoding="utf-8") as f:
|
|
105
|
+
f.write(json.dumps(event, ensure_ascii=False) + "\n")
|
|
106
|
+
except OSError:
|
|
107
|
+
logger.warning("Failed to write idle shutdown log: %s", path)
|
|
108
|
+
|
|
109
|
+
# pod ๊ฐ ๊ณง ์ฃฝ์ผ๋ฏ๋ก ์ฌ๊ธฐ๋ง ๋๊ธฐ ํ๋ฌ์๋ค (์ด์ #30). ์ง์ฐ ์ํ์ 2์ด์ด๊ณ
|
|
110
|
+
# ์คํจํด๋ ์์ธ๊ฐ ๋๊ฐ์ง ์๋๋ค โ ์ข
๋ฃ ๊ฒฝ๋ก๋ฅผ ๋ง์ผ๋ฉด ์ ๋๋ค.
|
|
111
|
+
from .analytics_ingest import enqueue_session_end
|
|
112
|
+
|
|
113
|
+
enqueue_session_end(event)
|
|
@@ -0,0 +1,519 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Resource Usage Utilities for the Jupyter server host.
|
|
3
|
+
|
|
4
|
+
Collects CPU, memory, disk, and GPU usage to guide client-side execution.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import os
|
|
10
|
+
import shutil
|
|
11
|
+
import subprocess
|
|
12
|
+
from typing import Optional
|
|
13
|
+
|
|
14
|
+
try:
|
|
15
|
+
import psutil
|
|
16
|
+
except ImportError: # pragma: no cover - optional dependency fallback
|
|
17
|
+
psutil = None
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _read_cgroup_value(path: str) -> Optional[str]:
|
|
21
|
+
try:
|
|
22
|
+
with open(path, "r", encoding="utf-8") as handle:
|
|
23
|
+
return handle.read().strip()
|
|
24
|
+
except Exception:
|
|
25
|
+
return None
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _format_gb(value_bytes: float) -> float:
|
|
29
|
+
return round(value_bytes / (1024**3), 2)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _safe_float(value: Optional[str]) -> Optional[float]:
|
|
33
|
+
if value is None:
|
|
34
|
+
return None
|
|
35
|
+
try:
|
|
36
|
+
return float(value)
|
|
37
|
+
except (TypeError, ValueError):
|
|
38
|
+
return None
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _safe_cpu_count() -> Optional[int]:
|
|
42
|
+
count = os.cpu_count()
|
|
43
|
+
return int(count) if count is not None else None
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def get_integrated_resources(
|
|
47
|
+
env_type: str = "auto", workspace_root: str = "."
|
|
48
|
+
) -> dict[str, object]:
|
|
49
|
+
"""
|
|
50
|
+
Collect resource usage snapshot for the Jupyter server host.
|
|
51
|
+
|
|
52
|
+
Args:
|
|
53
|
+
env_type: "auto", "pod", or "host" (auto detects Kubernetes)
|
|
54
|
+
workspace_root: Path used to compute disk usage
|
|
55
|
+
|
|
56
|
+
Returns:
|
|
57
|
+
JSON-serializable resource snapshot dict.
|
|
58
|
+
"""
|
|
59
|
+
is_pod = False
|
|
60
|
+
if env_type == "auto":
|
|
61
|
+
is_pod = os.path.exists("/var/run/secrets/kubernetes.io") or bool(
|
|
62
|
+
os.environ.get("KUBERNETES_SERVICE_HOST")
|
|
63
|
+
)
|
|
64
|
+
else:
|
|
65
|
+
is_pod = env_type.lower() == "pod"
|
|
66
|
+
|
|
67
|
+
environment = "Kubernetes Pod" if is_pod else "Host/VM"
|
|
68
|
+
cpu: dict[str, Optional[float]] = {"cores": None, "usage_percent": None}
|
|
69
|
+
memory: dict[str, Optional[float]] = {
|
|
70
|
+
"total_gb": None,
|
|
71
|
+
"available_gb": None,
|
|
72
|
+
"used_gb": None,
|
|
73
|
+
}
|
|
74
|
+
disk: dict[str, Optional[object]] = {
|
|
75
|
+
"path": None,
|
|
76
|
+
"total_gb": None,
|
|
77
|
+
"free_gb": None,
|
|
78
|
+
"used_gb": None,
|
|
79
|
+
}
|
|
80
|
+
gpus: list[dict[str, Optional[object]]] = []
|
|
81
|
+
gpu_status = "not_detected"
|
|
82
|
+
|
|
83
|
+
if is_pod:
|
|
84
|
+
try:
|
|
85
|
+
cpu_max = _read_cgroup_value("/sys/fs/cgroup/cpu.max")
|
|
86
|
+
if cpu_max:
|
|
87
|
+
quota, period = cpu_max.split()
|
|
88
|
+
if quota != "max":
|
|
89
|
+
cpu_limit = float(quota) / float(period)
|
|
90
|
+
else:
|
|
91
|
+
cpu_limit = psutil.cpu_count() if psutil else _safe_cpu_count() or 0
|
|
92
|
+
else:
|
|
93
|
+
cpu_limit = psutil.cpu_count() if psutil else _safe_cpu_count() or 0
|
|
94
|
+
|
|
95
|
+
mem_limit_raw = _read_cgroup_value("/sys/fs/cgroup/memory.max")
|
|
96
|
+
mem_current_raw = _read_cgroup_value("/sys/fs/cgroup/memory.current")
|
|
97
|
+
|
|
98
|
+
if mem_limit_raw and mem_limit_raw != "max":
|
|
99
|
+
mem_limit_gb = _format_gb(float(mem_limit_raw))
|
|
100
|
+
else:
|
|
101
|
+
mem_limit_gb = (
|
|
102
|
+
_format_gb(psutil.virtual_memory().total) if psutil else 0.0
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
mem_used_gb = _format_gb(float(mem_current_raw)) if mem_current_raw else 0.0
|
|
106
|
+
|
|
107
|
+
cpu["cores"] = round(cpu_limit, 2)
|
|
108
|
+
memory["total_gb"] = mem_limit_gb
|
|
109
|
+
memory["used_gb"] = mem_used_gb
|
|
110
|
+
if mem_limit_gb is not None and mem_used_gb is not None:
|
|
111
|
+
memory["available_gb"] = round(mem_limit_gb - mem_used_gb, 2)
|
|
112
|
+
except Exception:
|
|
113
|
+
cpu_count = psutil.cpu_count() if psutil else _safe_cpu_count()
|
|
114
|
+
cpu["cores"] = float(cpu_count) if cpu_count is not None else None
|
|
115
|
+
if psutil:
|
|
116
|
+
vm = psutil.virtual_memory()
|
|
117
|
+
memory["total_gb"] = _format_gb(vm.total)
|
|
118
|
+
memory["available_gb"] = _format_gb(vm.available)
|
|
119
|
+
memory["used_gb"] = _format_gb(vm.total - vm.available)
|
|
120
|
+
else:
|
|
121
|
+
cpu_count = psutil.cpu_count() if psutil else _safe_cpu_count()
|
|
122
|
+
cpu_percent = psutil.cpu_percent() if psutil else None
|
|
123
|
+
cpu["cores"] = float(cpu_count) if cpu_count is not None else None
|
|
124
|
+
cpu["usage_percent"] = cpu_percent
|
|
125
|
+
if psutil:
|
|
126
|
+
vm = psutil.virtual_memory()
|
|
127
|
+
memory["total_gb"] = _format_gb(vm.total)
|
|
128
|
+
memory["available_gb"] = _format_gb(vm.available)
|
|
129
|
+
memory["used_gb"] = _format_gb(vm.total - vm.available)
|
|
130
|
+
|
|
131
|
+
disk_path = workspace_root if workspace_root else "."
|
|
132
|
+
if not os.path.exists(disk_path):
|
|
133
|
+
disk_path = "."
|
|
134
|
+
disk["path"] = os.path.abspath(disk_path)
|
|
135
|
+
try:
|
|
136
|
+
total, used, free = shutil.disk_usage(disk_path)
|
|
137
|
+
disk["total_gb"] = _format_gb(total)
|
|
138
|
+
disk["free_gb"] = _format_gb(free)
|
|
139
|
+
disk["used_gb"] = _format_gb(used)
|
|
140
|
+
except Exception:
|
|
141
|
+
pass
|
|
142
|
+
|
|
143
|
+
if shutil.which("nvidia-smi"):
|
|
144
|
+
gpu_status = "available"
|
|
145
|
+
try:
|
|
146
|
+
result = subprocess.check_output(
|
|
147
|
+
[
|
|
148
|
+
"nvidia-smi",
|
|
149
|
+
"--query-gpu=name,utilization.gpu,memory.used,memory.total",
|
|
150
|
+
"--format=csv,noheader,nounits",
|
|
151
|
+
],
|
|
152
|
+
encoding="utf-8",
|
|
153
|
+
timeout=2,
|
|
154
|
+
).strip()
|
|
155
|
+
if result:
|
|
156
|
+
for line in result.split("\n"):
|
|
157
|
+
name, gpu_util, mem_used, mem_total = [
|
|
158
|
+
value.strip() for value in line.split(",")
|
|
159
|
+
]
|
|
160
|
+
gpus.append(
|
|
161
|
+
{
|
|
162
|
+
"name": name,
|
|
163
|
+
"utilization_percent": _safe_float(gpu_util),
|
|
164
|
+
"memory_used_mb": _safe_float(mem_used),
|
|
165
|
+
"memory_total_mb": _safe_float(mem_total),
|
|
166
|
+
}
|
|
167
|
+
)
|
|
168
|
+
except Exception:
|
|
169
|
+
gpu_status = "unavailable"
|
|
170
|
+
else:
|
|
171
|
+
gpu_status = "not_detected"
|
|
172
|
+
|
|
173
|
+
return {
|
|
174
|
+
"environment": environment,
|
|
175
|
+
"cpu": cpu,
|
|
176
|
+
"memory": memory,
|
|
177
|
+
"disk": disk,
|
|
178
|
+
"gpus": gpus,
|
|
179
|
+
"gpu_status": gpu_status,
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
# ---------------------------------------------------------------------------
|
|
184
|
+
# Resource Tier Classification & Guidance Generation
|
|
185
|
+
# ---------------------------------------------------------------------------
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def _classify_cpu(cores: Optional[float]) -> str:
|
|
189
|
+
"""Classify CPU into a tier based on core count."""
|
|
190
|
+
if cores is None:
|
|
191
|
+
return "unknown"
|
|
192
|
+
c = int(cores)
|
|
193
|
+
if c <= 3:
|
|
194
|
+
return "low"
|
|
195
|
+
if c <= 7:
|
|
196
|
+
return "medium"
|
|
197
|
+
if c <= 31:
|
|
198
|
+
return "high"
|
|
199
|
+
return "very_high"
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
def _classify_memory(total_gb: Optional[float]) -> str:
|
|
203
|
+
"""Classify memory into a tier based on total capacity."""
|
|
204
|
+
if total_gb is None:
|
|
205
|
+
return "unknown"
|
|
206
|
+
if total_gb < 4:
|
|
207
|
+
return "constrained"
|
|
208
|
+
if total_gb < 16:
|
|
209
|
+
return "limited"
|
|
210
|
+
if total_gb < 64:
|
|
211
|
+
return "comfortable"
|
|
212
|
+
if total_gb < 256:
|
|
213
|
+
return "large"
|
|
214
|
+
return "very_large"
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
def _classify_single_gpu(name: str, vram_mb: Optional[float]) -> str:
|
|
218
|
+
"""Classify a single GPU into a tier based on model name and VRAM."""
|
|
219
|
+
vram_gb = (vram_mb or 0) / 1024
|
|
220
|
+
upper = name.upper()
|
|
221
|
+
|
|
222
|
+
# Model-specific overrides (inference-optimized GPUs are entry-tier for training)
|
|
223
|
+
if "L4" in upper or "T4" in upper:
|
|
224
|
+
return "entry"
|
|
225
|
+
if "H200" in upper or "H100" in upper:
|
|
226
|
+
return "ultra"
|
|
227
|
+
# A100/V100: differentiate by VRAM
|
|
228
|
+
if "A100" in upper:
|
|
229
|
+
return "ultra" if vram_gb >= 48 else "high"
|
|
230
|
+
if "V100" in upper:
|
|
231
|
+
return "high" if vram_gb >= 24 else "mid"
|
|
232
|
+
|
|
233
|
+
# Fallback: classify by VRAM size
|
|
234
|
+
if vram_gb < 16:
|
|
235
|
+
return "entry"
|
|
236
|
+
if vram_gb < 24:
|
|
237
|
+
return "mid"
|
|
238
|
+
if vram_gb < 48:
|
|
239
|
+
return "high"
|
|
240
|
+
return "ultra"
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
def _classify_gpu(gpus: list) -> dict:
|
|
244
|
+
"""Classify GPU setup into a tier dict."""
|
|
245
|
+
if not gpus:
|
|
246
|
+
return {"tier": "none", "count": 0, "details": []}
|
|
247
|
+
|
|
248
|
+
details = []
|
|
249
|
+
for g in gpus:
|
|
250
|
+
name = g.get("name", "GPU")
|
|
251
|
+
vram_mb = g.get("memory_total_mb")
|
|
252
|
+
tier = _classify_single_gpu(name, vram_mb)
|
|
253
|
+
details.append({"name": name, "vram_mb": vram_mb, "tier": tier})
|
|
254
|
+
|
|
255
|
+
count = len(details)
|
|
256
|
+
# Overall GPU tier: use the best single-GPU tier
|
|
257
|
+
tier_order = ["entry", "mid", "high", "ultra"]
|
|
258
|
+
best_tier = max(details, key=lambda d: tier_order.index(d["tier"]))["tier"]
|
|
259
|
+
|
|
260
|
+
return {
|
|
261
|
+
"tier": best_tier,
|
|
262
|
+
"count": count,
|
|
263
|
+
"multi_gpu": count > 1,
|
|
264
|
+
"details": details,
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
def classify_resource_tiers(data: dict) -> dict:
|
|
269
|
+
"""Classify hardware resources into granular tiers for LLM guidance.
|
|
270
|
+
|
|
271
|
+
Args:
|
|
272
|
+
data: Output of ``get_integrated_resources()``.
|
|
273
|
+
|
|
274
|
+
Returns:
|
|
275
|
+
Dict with ``cpu_tier``, ``mem_tier``, ``gpu`` (sub-dict), and raw values.
|
|
276
|
+
"""
|
|
277
|
+
cpu = data.get("cpu", {})
|
|
278
|
+
mem = data.get("memory", {})
|
|
279
|
+
gpus = data.get("gpus", [])
|
|
280
|
+
|
|
281
|
+
cores = cpu.get("cores")
|
|
282
|
+
total_gb = mem.get("total_gb")
|
|
283
|
+
avail_gb = mem.get("available_gb")
|
|
284
|
+
|
|
285
|
+
return {
|
|
286
|
+
"cpu_tier": _classify_cpu(cores),
|
|
287
|
+
"mem_tier": _classify_memory(total_gb),
|
|
288
|
+
"gpu": _classify_gpu(gpus),
|
|
289
|
+
"cores": cores,
|
|
290
|
+
"total_gb": total_gb,
|
|
291
|
+
"avail_gb": avail_gb,
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
# --- Guidance text generators ------------------------------------------------
|
|
296
|
+
|
|
297
|
+
_CPU_GUIDANCE = {
|
|
298
|
+
"low": (
|
|
299
|
+
"- CPU {cores}์ฝ์ด (์ ์ฌ์): ๋ณ๋ ฌ์ฒ๋ฆฌ ์ค๋ฒํค๋๊ฐ ์ด๋์ ์ด๊ณผํฉ๋๋ค. "
|
|
300
|
+
"์๊ณ ๋ฆฌ์ฆ ๋ณต์ก๋ ๊ฐ์ (O(nยฒ)โO(n log n))๊ณผ ๋ฒกํฐ ์ฐ์ฐ(numpy/pandas vectorized ops)์ ์ง์คํ์ธ์. "
|
|
301
|
+
"multiprocessing/joblib ์ฌ์ฉ์ ์ง์ํ์ธ์."
|
|
302
|
+
),
|
|
303
|
+
"medium": (
|
|
304
|
+
"- CPU {cores}์ฝ์ด (์ค๊ฐ): joblib(n_jobs={n_jobs})๋ก sklearn ๊ต์ฐจ๊ฒ์ฆยทํ์ดํผํ๋ผ๋ฏธํฐ ํ์์ ๋ณ๋ ฌํํ์ธ์. "
|
|
305
|
+
"CPU ๋ฐ์ด๋ ์ ์ฒ๋ฆฌ๋ multiprocessing.Pool(processes={n_jobs})๋ก ๋ถํ ๊ฐ๋ฅํฉ๋๋ค. "
|
|
306
|
+
"I/O ๋ฐ์ด๋ ์์
์ threading ๋๋ asyncio๊ฐ ๋ ํจ๊ณผ์ ์
๋๋ค."
|
|
307
|
+
),
|
|
308
|
+
"high": (
|
|
309
|
+
"- CPU {cores}์ฝ์ด (๊ณ ์ฌ์): joblib(n_jobs=-1)๋ก ๋ชจ๋ sklearn ๋ณ๋ ฌ ์ฐ์ฐ์ ์ต๋ํํ์ธ์. "
|
|
310
|
+
"๋์ฉ๋ DataFrame ์ฒ๋ฆฌ ์ Dask LocalCluster(n_workers={workers}, threads_per_worker=2)๋ฅผ ํ์ฉํ์ธ์. "
|
|
311
|
+
"Polars์ ์๋ ๋ฉํฐ์ค๋ ๋ฉ์ด {cores}์ฝ์ด๋ฅผ ์์ ํ ํ์ฉํฉ๋๋ค. "
|
|
312
|
+
"๊ต์ฐจ๊ฒ์ฆยท์์๋ธ ํ์ต์ ๋ณ๋ ฌ ์คํํ์ธ์."
|
|
313
|
+
),
|
|
314
|
+
"very_high": (
|
|
315
|
+
"- CPU {cores}์ฝ์ด (์๋ฒ๊ธ): Dask/Ray ๋ถ์ฐ ์ค์ผ์ค๋ฌ๋ก DataFrame ์ฐ์ฐ์ ๋ณ๋ ฌํํ์ธ์. "
|
|
316
|
+
"Ray๋ฅผ ์ฌ์ฉํ ๋ถ์ฐ ํ์ดํผํ๋ผ๋ฏธํฐ ํ๋(ray.tune)์ ๊ถ์ฅํฉ๋๋ค. "
|
|
317
|
+
"Spark DataFrame์ผ๋ก ๋๊ท๋ชจ ETL ํ์ดํ๋ผ์ธ์ ๊ตฌ์ฑํ์ธ์. "
|
|
318
|
+
"๋ฉํฐ ๋ชจ๋ธ ์์๋ธ์ ๋์ ํ์ตํ์ธ์(joblib n_jobs=-1 ๋๋ Ray)."
|
|
319
|
+
),
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
_MEM_GUIDANCE = {
|
|
323
|
+
"constrained": (
|
|
324
|
+
"- ๊ฐ์ฉ ๋ฉ๋ชจ๋ฆฌ {avail_gb:.1f}GB / ์ ์ฒด {total_gb:.1f}GB (์ ํ์ ): "
|
|
325
|
+
"OOM ์ํ ๋์. pd.read_csv(chunksize=10000) ํ์, DataFrame ์ ์ฒด ๋ก๋ ๊ธ์ง. "
|
|
326
|
+
"del df; gc.collect() ์ ๊ทน ์ฌ์ฉ. generator/iterator ํจํด ์ฐ์ . "
|
|
327
|
+
"float64โfloat32, objectโcategory๋ก dtypes ์ฆ์ ๋ณํ."
|
|
328
|
+
),
|
|
329
|
+
"limited": (
|
|
330
|
+
"- ๊ฐ์ฉ ๋ฉ๋ชจ๋ฆฌ {avail_gb:.1f}GB / ์ ์ฒด {total_gb:.1f}GB (๋ณดํต): "
|
|
331
|
+
"dtypes ์ต์ ํ ํ์(float64โfloat32: ~50% ์ ๊ฐ, objectโcategory). "
|
|
332
|
+
"usecols๋ก ํ์ ์ปฌ๋ผ๋ง ๋ก๋. ๋ถํ์ํ ์ค๊ฐ DataFrame ์ฆ์ del. "
|
|
333
|
+
"๋ฐ์ดํฐ์
์ด ๊ฐ์ฉ ๋ฉ๋ชจ๋ฆฌ์ 30% ์ด๊ณผ ์ chunked ์ฒ๋ฆฌ ๊ถ์ฅ."
|
|
334
|
+
),
|
|
335
|
+
"comfortable": (
|
|
336
|
+
"- ๊ฐ์ฉ ๋ฉ๋ชจ๋ฆฌ {avail_gb:.1f}GB / ์ ์ฒด {total_gb:.1f}GB (์ฌ์ ): "
|
|
337
|
+
"~{safe_size:.0f}GB ์ดํ ๋ฐ์ดํฐ์
์ ์ ์ฒด ๋ก๋ ๊ฐ๋ฅ. "
|
|
338
|
+
"Polars๋ฅผ pandas ๋์ ์ฌ์ฉํ๋ฉด ๋ฉ๋ชจ๋ฆฌ ํจ์จ 2-5๋ฐฐ ๊ฐ์ . "
|
|
339
|
+
"dtypes ์ต์ ํ ๊ถ์ฅ(ํ์ ์๋). ๋๊ท๋ชจ ์กฐ์ธ ์ ๋ฉ๋ชจ๋ฆฌ ํผํฌ ์ฃผ์."
|
|
340
|
+
),
|
|
341
|
+
"large": (
|
|
342
|
+
"- ๊ฐ์ฉ ๋ฉ๋ชจ๋ฆฌ {avail_gb:.1f}GB / ์ ์ฒด {total_gb:.1f}GB (๋์ฉ๋): "
|
|
343
|
+
"๋๋ถ๋ถ์ DS ๋ฐ์ดํฐ์
์ ์ ์ฒด ๋ก๋ ๊ฐ๋ฅ. "
|
|
344
|
+
"Polars๋ฅผ pandas ๋์ ์ฌ์ฉํ๋ฉด ์ฒ๋ฆฌ ์๋ 2-10๋ฐฐ ๊ฐ์ (๋ฉํฐ์ค๋ ๋ ํ์ฉ). "
|
|
345
|
+
"pd.read_parquet(engine='pyarrow')๋ก ๋ณ๋ ฌ ์ฝ๊ธฐ. "
|
|
346
|
+
"๋ฉ๋ชจ๋ฆฌ ์ฌ์ ๊ฐ ์์ผ๋ฏ๋ก ์บ์ฑยทmemoization ์ ๊ทน ํ์ฉ."
|
|
347
|
+
),
|
|
348
|
+
"very_large": (
|
|
349
|
+
"- ๊ฐ์ฉ ๋ฉ๋ชจ๋ฆฌ {avail_gb:.1f}GB / ์ ์ฒด {total_gb:.1f}GB (์ด๋์ฉ๋): "
|
|
350
|
+
"๋ฉ๋ชจ๋ฆฌ ๋ถ์กฑ์ ์ฌ์ค์ ์์. ๋ถ์ฐ ์ปดํจํ
์ ์๋๋ฅผ ์ํด ์ฌ์ฉ(OOM ๋ฐฉ์ง๊ฐ ์๋). "
|
|
351
|
+
"Dask/Spark๋ก ๋ณ๋ ฌ I/O์ ์ฐ์ฐ์ ๊ฐ์ํ์ธ์. "
|
|
352
|
+
"Polars/cuDF๋ก ๋จ์ผ ๋จธ์ ์ต๋ ์ฑ๋ฅ์ ์ถ์ถํ์ธ์. "
|
|
353
|
+
"๋๊ท๋ชจ ํผ์ฒ ์์ง๋์ด๋ง์ ์ธ๋ฉ๋ชจ๋ฆฌ๋ก ์์ ๋กญ๊ฒ ์ํ ๊ฐ๋ฅ."
|
|
354
|
+
),
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
_GPU_GUIDANCE = {
|
|
358
|
+
"entry": (
|
|
359
|
+
"- GPU {name} ({vram_gb:.0f}GB VRAM, entry๊ธ): "
|
|
360
|
+
"mixed precision(fp16/bf16) ํ์. gradient checkpointing ํ์. batch_size ์ต์ํ. "
|
|
361
|
+
"ํฐ ๋ชจ๋ธ ํ์ต ๋ถ๊ฐโํ์ธํ๋ ์ LoRA/QLoRA ํ์. "
|
|
362
|
+
"cuDF๋ ์๊ท๋ชจ ๋ฐ์ดํฐ์๋ง ์ฌ์ฉ ๊ฐ๋ฅ."
|
|
363
|
+
),
|
|
364
|
+
"mid": (
|
|
365
|
+
"- GPU {name} ({vram_gb:.0f}GB VRAM, mid๊ธ): "
|
|
366
|
+
"mixed precision ๊ฐ๋ ฅ ๊ถ์ฅ. ์ค๊ฐ ๊ท๋ชจ ๋ชจ๋ธ ํ์ต ๊ฐ๋ฅ. "
|
|
367
|
+
"gradient accumulation์ผ๋ก effective batch size ํ๋. "
|
|
368
|
+
"DeepSpeed ZeRO Stage 1 ํ์ฉ. cuDF๋ก ์ค๊ฐ ๊ท๋ชจ DataFrame GPU ๊ฐ์."
|
|
369
|
+
),
|
|
370
|
+
"high": (
|
|
371
|
+
"- GPU {name} ({vram_gb:.0f}GB VRAM, high๊ธ): "
|
|
372
|
+
"full precision ๊ฐ๋ฅํ๋ fp16 ๊ถ์ฅ(์๋ ์ด์ ). ๋๋ถ๋ถ์ ๋ชจ๋ธ ํ์ต ๊ฐ๋ฅ. "
|
|
373
|
+
"cuDF๋ก DataFrame GPU ๊ฐ์. RAPIDS(cuML, cuGraph) ํ์ฉ์ผ๋ก "
|
|
374
|
+
"sklearn ๋๋น 10-100๋ฐฐ ๊ฐ์."
|
|
375
|
+
),
|
|
376
|
+
"ultra": (
|
|
377
|
+
"- GPU {name} ({vram_gb:.0f}GB VRAM, ultra๊ธ): "
|
|
378
|
+
"๋๊ท๋ชจ ๋ชจ๋ธ ํ์ต/์ถ๋ก ๊ฐ๋ฅ. flash attention ํ์ฉ. "
|
|
379
|
+
"cuDF/cuML ์ ๊ทน ํ์ฉ์ผ๋ก ์ ์ฒด DS ํ์ดํ๋ผ์ธ GPU ๊ฐ์. "
|
|
380
|
+
"๋ชจ๋ธ ๋ณ๋ ฌ์ฒ๋ฆฌ(tensor parallelism) ๊ฐ๋ฅ."
|
|
381
|
+
),
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
|
|
385
|
+
def _gpu_guidance_lines(gpu_info: dict) -> list[str]:
|
|
386
|
+
"""Generate GPU-specific guidance lines."""
|
|
387
|
+
lines: list[str] = []
|
|
388
|
+
if gpu_info["tier"] == "none":
|
|
389
|
+
lines.append(
|
|
390
|
+
"- GPU ๋ฏธ๊ฐ์ง: GPU ์์กด ์ฝ๋๊ฐ ์๋ค๋ฉด device='cpu' ํด๋ฐฑ ํ์ธ. "
|
|
391
|
+
"sklearn/XGBoost CPU ๋ฒ์ ์ฌ์ฉ. cuDF/RAPIDS ์ฌ์ฉ ๋ถ๊ฐ."
|
|
392
|
+
)
|
|
393
|
+
return lines
|
|
394
|
+
|
|
395
|
+
# Per-GPU guidance
|
|
396
|
+
for detail in gpu_info["details"]:
|
|
397
|
+
name = detail["name"]
|
|
398
|
+
vram_gb = (detail["vram_mb"] or 0) / 1024
|
|
399
|
+
tier = detail["tier"]
|
|
400
|
+
template = _GPU_GUIDANCE.get(tier, _GPU_GUIDANCE["entry"])
|
|
401
|
+
lines.append(template.format(name=name, vram_gb=vram_gb))
|
|
402
|
+
|
|
403
|
+
# Multi-GPU additional guidance
|
|
404
|
+
if gpu_info["multi_gpu"]:
|
|
405
|
+
count = gpu_info["count"]
|
|
406
|
+
lines.append(
|
|
407
|
+
f"- ๋ฉํฐGPU ({count}์ฅ): ๋ฐ์ดํฐ ๋ณ๋ ฌ์ฒ๋ฆฌ(DDP) ๊ธฐ๋ณธ ์ ์ฉ. "
|
|
408
|
+
f"GPU {count}์ฅ ํ์ฉ ์ FSDP ๋๋ DeepSpeed ZeRO Stage 3 ๊ถ์ฅ. "
|
|
409
|
+
"NCCL ํต์ ์ต์ ํ. ๋๊ท๋ชจ ๋ชจ๋ธ์ tensor/pipeline parallelism ์ ์ฉ."
|
|
410
|
+
)
|
|
411
|
+
|
|
412
|
+
return lines
|
|
413
|
+
|
|
414
|
+
|
|
415
|
+
def _combined_tech_recommendation(cpu_tier: str, mem_tier: str, gpu_info: dict) -> str:
|
|
416
|
+
"""Generate cross-cutting technology stack recommendation."""
|
|
417
|
+
gpu_tier = gpu_info["tier"]
|
|
418
|
+
|
|
419
|
+
# DataFrame library recommendation
|
|
420
|
+
if gpu_tier in ("high", "ultra"):
|
|
421
|
+
df_rec = "cuDF(GPU ๊ฐ์) ๊ธฐ๋ณธ. Polars(CPU ํด๋ฐฑ). pandas๋ ์๊ท๋ชจ ํ์์๋ง."
|
|
422
|
+
elif mem_tier in ("large", "very_large") and cpu_tier in ("high", "very_high"):
|
|
423
|
+
df_rec = "Polars(๊ธฐ๋ณธ) โ ๋ฉํฐ์ค๋ ๋๋ก ๋์ฉ๋ ๊ณ ์ ์ฒ๋ฆฌ. pandas๋ ์๊ท๋ชจ์๋ง."
|
|
424
|
+
elif mem_tier in ("comfortable", "large"):
|
|
425
|
+
df_rec = "Polars ๊ถ์ฅ(์๋ ์ด์ ). pandas๋ ์ ์ ๊ท๋ชจ์์ ์ฌ์ฉ ๊ฐ๋ฅ."
|
|
426
|
+
elif mem_tier == "limited":
|
|
427
|
+
df_rec = "pandas + dtypes ์ต์ ํ. ๋์ฉ๋ ์ Dask DataFrame์ผ๋ก ์ ํ."
|
|
428
|
+
else:
|
|
429
|
+
df_rec = "pandas chunked ์ฒ๋ฆฌ ํ์. ๊ฐ๋ฅํ๋ฉด Polars(๋ฉ๋ชจ๋ฆฌ ํจ์จ ์ฐ์)."
|
|
430
|
+
|
|
431
|
+
# Parallelism recommendation
|
|
432
|
+
if cpu_tier == "very_high":
|
|
433
|
+
par_rec = "Ray/Dask ๋ถ์ฐ ์ฒ๋ฆฌ. joblib(n_jobs=-1) + Ray ๋ฐฑ์๋."
|
|
434
|
+
elif cpu_tier == "high":
|
|
435
|
+
par_rec = "joblib(n_jobs=-1). ๋๊ท๋ชจ ์์
์ Dask LocalCluster."
|
|
436
|
+
elif cpu_tier == "medium":
|
|
437
|
+
par_rec = "joblib(n_jobs=cores-1). multiprocessing.Pool ์ ํ์ ."
|
|
438
|
+
else:
|
|
439
|
+
par_rec = "๋ณ๋ ฌ์ฒ๋ฆฌ ๋น๊ถ์ฅ. ๋ฒกํฐ ์ฐ์ฐ์ผ๋ก ๋์ฒด."
|
|
440
|
+
|
|
441
|
+
# ML training recommendation
|
|
442
|
+
if gpu_tier == "ultra" and gpu_info.get("multi_gpu"):
|
|
443
|
+
ml_rec = "FSDP/DeepSpeed ๋ถ์ฐ ํ์ต. ๋๊ท๋ชจ ๋ชจ๋ธ full training ๊ฐ๋ฅ."
|
|
444
|
+
elif gpu_tier == "ultra":
|
|
445
|
+
ml_rec = "๋จ์ผ GPU๋ก ๋๋ถ๋ถ์ ๋ชจ๋ธ full training. flash attention ํ์ฉ."
|
|
446
|
+
elif gpu_tier == "high":
|
|
447
|
+
ml_rec = "full training ๊ฐ๋ฅ. RAPIDS cuML๋ก ์ ํต ML ๊ฐ์."
|
|
448
|
+
elif gpu_tier == "mid":
|
|
449
|
+
ml_rec = "mixed precision ํ์ต. gradient accumulation ํ์."
|
|
450
|
+
elif gpu_tier == "entry":
|
|
451
|
+
ml_rec = "LoRA/QLoRA ํ์ธํ๋๋ง. ํฐ ๋ชจ๋ธ full training ๋ถ๊ฐ."
|
|
452
|
+
else:
|
|
453
|
+
ml_rec = "CPU ํ์ต. sklearn n_jobs ํ์ฉ. XGBoost hist/gpu_hist ๋ถ๊ฐ."
|
|
454
|
+
|
|
455
|
+
return (
|
|
456
|
+
f"- ๐ ๊ถ์ฅ DataFrame ๋ผ์ด๋ธ๋ฌ๋ฆฌ: {df_rec}\n"
|
|
457
|
+
f"- โก ๋ณ๋ ฌ ์ฒ๋ฆฌ ์ ๋ต: {par_rec}\n"
|
|
458
|
+
f"- ๐ง ML ํ์ต ์ ๋ต: {ml_rec}"
|
|
459
|
+
)
|
|
460
|
+
|
|
461
|
+
|
|
462
|
+
def generate_tier_guidance(tiers: dict, mode: str = "review") -> str:
|
|
463
|
+
"""Generate Korean guidance text based on classified resource tiers.
|
|
464
|
+
|
|
465
|
+
Args:
|
|
466
|
+
tiers: Output of ``classify_resource_tiers()``.
|
|
467
|
+
mode: ``"review"`` for code review diagnostics,
|
|
468
|
+
``"codegen"`` for imperative code generation instructions.
|
|
469
|
+
|
|
470
|
+
Returns:
|
|
471
|
+
Multi-line Korean guidance string.
|
|
472
|
+
"""
|
|
473
|
+
lines: list[str] = []
|
|
474
|
+
cpu_tier = tiers.get("cpu_tier", "unknown")
|
|
475
|
+
mem_tier = tiers.get("mem_tier", "unknown")
|
|
476
|
+
gpu_info = tiers.get("gpu", {"tier": "none", "count": 0, "details": []})
|
|
477
|
+
cores = tiers.get("cores")
|
|
478
|
+
total_gb = tiers.get("total_gb") or 0
|
|
479
|
+
avail_gb = tiers.get("avail_gb") or 0
|
|
480
|
+
|
|
481
|
+
# CPU guidance
|
|
482
|
+
cpu_template = _CPU_GUIDANCE.get(cpu_tier)
|
|
483
|
+
if cpu_template and cores is not None:
|
|
484
|
+
n_jobs = max(1, int(cores) - 1)
|
|
485
|
+
workers = max(1, int(cores) // 2)
|
|
486
|
+
lines.append(
|
|
487
|
+
cpu_template.format(cores=int(cores), n_jobs=n_jobs, workers=workers)
|
|
488
|
+
)
|
|
489
|
+
|
|
490
|
+
# Memory guidance
|
|
491
|
+
mem_template = _MEM_GUIDANCE.get(mem_tier)
|
|
492
|
+
if mem_template:
|
|
493
|
+
safe_size = total_gb * 0.3
|
|
494
|
+
lines.append(
|
|
495
|
+
mem_template.format(
|
|
496
|
+
avail_gb=avail_gb, total_gb=total_gb, safe_size=safe_size
|
|
497
|
+
)
|
|
498
|
+
)
|
|
499
|
+
|
|
500
|
+
# Memory pressure warning (avail < 30% of total)
|
|
501
|
+
if total_gb > 0 and avail_gb > 0 and avail_gb < total_gb * 0.3:
|
|
502
|
+
lines.append(
|
|
503
|
+
f"- โ ๏ธ ๋ฉ๋ชจ๋ฆฌ ์๋ฐ: ๊ฐ์ฉ {avail_gb:.1f}GB / ์ ์ฒด {total_gb:.1f}GB "
|
|
504
|
+
f"({avail_gb / total_gb * 100:.0f}%). ํ์ฌ ์ํ์์๋ "
|
|
505
|
+
"ํ ๋จ๊ณ ๋ฎ์ ๋ฉ๋ชจ๋ฆฌ ์ ๋ต์ ์ ์ฉํ์ธ์."
|
|
506
|
+
)
|
|
507
|
+
|
|
508
|
+
# GPU guidance
|
|
509
|
+
lines.extend(_gpu_guidance_lines(gpu_info))
|
|
510
|
+
|
|
511
|
+
# Combined technology recommendation
|
|
512
|
+
lines.append("")
|
|
513
|
+
header = (
|
|
514
|
+
"**ํ๊ฒฝ ๋ง์ถค ๊ธฐ์ ์คํ ๊ถ์ฅ**:" if mode == "review" else "**๊ธฐ์ ์คํ ์ง์นจ**:"
|
|
515
|
+
)
|
|
516
|
+
lines.append(header)
|
|
517
|
+
lines.append(_combined_tech_recommendation(cpu_tier, mem_tier, gpu_info))
|
|
518
|
+
|
|
519
|
+
return "\n".join(lines)
|