flexsds 0.2.0__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.
- flexsds/__init__.py +4 -0
- flexsds/assets/ptit_logo.png +0 -0
- flexsds/cli/__init__.py +1 -0
- flexsds/cli/banner.py +84 -0
- flexsds/cli/main.py +330 -0
- flexsds/cli/output.py +69 -0
- flexsds/cli/task_runner.py +171 -0
- flexsds/cli/wizard.py +360 -0
- flexsds/client/__init__.py +4 -0
- flexsds/client/chat.py +137 -0
- flexsds/client/client.py +107 -0
- flexsds/client/monitor.py +95 -0
- flexsds/core/__init__.py +44 -0
- flexsds/core/compatibility.py +442 -0
- flexsds/core/controller.py +502 -0
- flexsds/core/cost_model.py +530 -0
- flexsds/core/credentials.py +111 -0
- flexsds/core/hardware.py +77 -0
- flexsds/core/tree_topology.py +296 -0
- flexsds/profiles/profile_sm75_t4.json +62 -0
- flexsds/profiles/profile_sm89_l4.json +194 -0
- flexsds/server/__init__.py +1 -0
- flexsds/server/colab_runner.py +62 -0
- flexsds/server/local_runner.py +208 -0
- flexsds/server/modal_app.py +50 -0
- flexsds/server/modal_runner.py +98 -0
- flexsds/server/vscode_cli.py +82 -0
- flexsds-0.2.0.dist-info/METADATA +204 -0
- flexsds-0.2.0.dist-info/RECORD +62 -0
- flexsds-0.2.0.dist-info/WHEEL +5 -0
- flexsds-0.2.0.dist-info/entry_points.txt +2 -0
- flexsds-0.2.0.dist-info/top_level.txt +4 -0
- pipeline/entity_eval.py +171 -0
- pipeline/examples/node1_extractor_input_m0_04_001.txt +9 -0
- pipeline/examples/node1_extractor_output_m0_04_001.json +90 -0
- pipeline/extractor.py +502 -0
- pipeline/invoice_models.py +163 -0
- pipeline/node2_questions.py +30 -0
- pipeline/openai_compatible.py +87 -0
- pipeline/prompts/README.md +18 -0
- pipeline/prompts/node1_extractor_system.md +92 -0
- pipeline/prompts/node3_report_user.md +5 -0
- pipeline/report_writer.py +143 -0
- pipeline/schemas/invoice_extraction.schema.json +469 -0
- pipeline/sql_guard.py +140 -0
- pipeline/sqlite/schema.sql +219 -0
- pipeline/text_to_sql.py +170 -0
- pipeline/validation.py +166 -0
- pipeline/w1_workload.py +139 -0
- proposer/__init__.py +31 -0
- proposer/adapter.py +153 -0
- proposer/controller.py +460 -0
- proposer/cost_model.py +216 -0
- proposer/tree_adaptive.py +466 -0
- server/__init__.py +3 -0
- server/adaptive_scheduler.py +215 -0
- server/budget_planner.py +287 -0
- server/lineage.py +290 -0
- server/lineage_diagnostics.py +38 -0
- server/lineage_executor.py +129 -0
- server/lineage_startup.py +47 -0
- server/t4_rowwise_attention.py +262 -0
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
"""Local serving runner and zero-zombie process management."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import json
|
|
5
|
+
import os
|
|
6
|
+
import signal
|
|
7
|
+
import socket
|
|
8
|
+
import subprocess
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import Any, Dict, List, Optional
|
|
11
|
+
|
|
12
|
+
try:
|
|
13
|
+
from flexsds.cli.output import print_output as print
|
|
14
|
+
except ModuleNotFoundError:
|
|
15
|
+
from src.flexsds.cli.output import print_output as print
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def finish_launch(proc, background: bool = False) -> int:
|
|
19
|
+
"""Propagate startup/foreground failure without equating a process with readiness."""
|
|
20
|
+
if proc is None:
|
|
21
|
+
print("Launch failed; no serving readiness established.")
|
|
22
|
+
return 1
|
|
23
|
+
print("Serving process started; endpoint readiness is not verified.")
|
|
24
|
+
try:
|
|
25
|
+
code = proc.poll() if background else proc.wait()
|
|
26
|
+
return code if isinstance(code, int) else 0
|
|
27
|
+
except KeyboardInterrupt:
|
|
28
|
+
proc.terminate()
|
|
29
|
+
try:
|
|
30
|
+
proc.wait(timeout=15)
|
|
31
|
+
except subprocess.TimeoutExpired:
|
|
32
|
+
proc.kill()
|
|
33
|
+
proc.wait()
|
|
34
|
+
return 130
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class LocalServingRunner:
|
|
38
|
+
"""Khởi chạy và quản lý vòng đời máy chủ vLLM cục bộ với FlexSDS."""
|
|
39
|
+
|
|
40
|
+
def build_command(self, config: Dict[str, Any], port: Optional[int] = None) -> List[str]:
|
|
41
|
+
"""Tạo danh sách đối số dòng lệnh chạy vLLM phục vụ mô hình."""
|
|
42
|
+
serving_cfg = config.get("serving", config)
|
|
43
|
+
target_model = serving_cfg.get("target_model", "Qwen/Qwen2.5-7B-Instruct-AWQ")
|
|
44
|
+
draft_model = serving_cfg.get("draft_model", "duckhoi/qwen2.5-7b-eagle3.1-speculator")
|
|
45
|
+
draft_method = serving_cfg.get("draft_method", "eagle3")
|
|
46
|
+
use_hetero = serving_cfg.get("use_heterogeneous_vocab", True)
|
|
47
|
+
|
|
48
|
+
effective_port = port if port is not None else serving_cfg.get("port", 8000)
|
|
49
|
+
target_lower = target_model.lower()
|
|
50
|
+
|
|
51
|
+
# Xác định quantization và dtype
|
|
52
|
+
if "awq" in target_lower:
|
|
53
|
+
quantization = "awq_marlin"
|
|
54
|
+
dtype = "bfloat16"
|
|
55
|
+
elif "gptq" in target_lower:
|
|
56
|
+
quantization = "gptq"
|
|
57
|
+
dtype = "float16"
|
|
58
|
+
else:
|
|
59
|
+
quantization = serving_cfg.get("quantization", "none")
|
|
60
|
+
dtype = serving_cfg.get("dtype", "bfloat16")
|
|
61
|
+
|
|
62
|
+
speculative_config: Dict[str, Any] = {
|
|
63
|
+
"method": draft_method,
|
|
64
|
+
"model": draft_model,
|
|
65
|
+
"num_speculative_tokens": 4,
|
|
66
|
+
"num_speculative_tokens_per_batch_size": [[1, 32, 4]],
|
|
67
|
+
"draft_sample_method": "greedy",
|
|
68
|
+
}
|
|
69
|
+
if use_hetero:
|
|
70
|
+
speculative_config["use_heterogeneous_vocab"] = True
|
|
71
|
+
|
|
72
|
+
cmd: List[str] = [
|
|
73
|
+
"vllm",
|
|
74
|
+
"serve",
|
|
75
|
+
target_model,
|
|
76
|
+
"--host",
|
|
77
|
+
"0.0.0.0",
|
|
78
|
+
"--port",
|
|
79
|
+
str(effective_port),
|
|
80
|
+
"--served-model-name",
|
|
81
|
+
serving_cfg.get("served_model_name", "qwen2.5-7b-adaptive"),
|
|
82
|
+
"--dtype",
|
|
83
|
+
dtype,
|
|
84
|
+
"--generation-config",
|
|
85
|
+
"vllm",
|
|
86
|
+
"--gpu-memory-utilization",
|
|
87
|
+
str(serving_cfg.get("gpu_memory_utilization", 0.85)),
|
|
88
|
+
"--max-model-len",
|
|
89
|
+
str(serving_cfg.get("max_model_len", 4096)),
|
|
90
|
+
"--max-num-seqs",
|
|
91
|
+
str(serving_cfg.get("max_num_seqs", 32)),
|
|
92
|
+
]
|
|
93
|
+
|
|
94
|
+
if quantization != "none":
|
|
95
|
+
cmd.extend(["--quantization", quantization])
|
|
96
|
+
|
|
97
|
+
if serving_cfg.get("enable_adaptive", True):
|
|
98
|
+
cmd.extend([
|
|
99
|
+
"--speculative-config", json.dumps(speculative_config),
|
|
100
|
+
"--scheduler-cls", "src.server.adaptive_scheduler.AdaptiveAsyncScheduler",
|
|
101
|
+
"--distributed-executor-backend", "src.server.lineage_executor.LineageUniProcExecutor",
|
|
102
|
+
"--per-request-spec-decode-metrics", "summary",
|
|
103
|
+
])
|
|
104
|
+
else:
|
|
105
|
+
# Static speculation uses vLLM's default scheduler/executor, not the adaptive pair.
|
|
106
|
+
speculative_config.pop("num_speculative_tokens_per_batch_size", None)
|
|
107
|
+
cmd.extend(["--speculative-config", json.dumps(speculative_config)])
|
|
108
|
+
|
|
109
|
+
return cmd
|
|
110
|
+
|
|
111
|
+
def check_port_available(self, port: int) -> bool:
|
|
112
|
+
"""Kiểm tra xem cổng port có đang khả dụng không."""
|
|
113
|
+
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
|
|
114
|
+
try:
|
|
115
|
+
s.bind(("", port))
|
|
116
|
+
return True
|
|
117
|
+
except OSError:
|
|
118
|
+
return False
|
|
119
|
+
|
|
120
|
+
def setup_environment(self, config: Dict[str, Any]) -> Dict[str, str]:
|
|
121
|
+
"""Thiết lập các biến môi trường cho FlexSDS Adaptive Scheduler."""
|
|
122
|
+
serving_cfg = config.get("serving", config)
|
|
123
|
+
env = dict(os.environ)
|
|
124
|
+
project_root = str(Path(__file__).resolve().parents[3])
|
|
125
|
+
existing_pythonpath = env.get("PYTHONPATH", "")
|
|
126
|
+
if project_root not in existing_pythonpath:
|
|
127
|
+
env["PYTHONPATH"] = f"{project_root}{os.pathsep}{existing_pythonpath}" if existing_pythonpath else project_root
|
|
128
|
+
env["ADAPTIVE_ENABLE_VRAM_GUARD"] = "1"
|
|
129
|
+
env["ADAPTIVE_TELEMETRY_PATH"] = os.path.abspath(
|
|
130
|
+
serving_cfg.get("telemetry_path", "telemetry/serving_trace.jsonl")
|
|
131
|
+
)
|
|
132
|
+
env["ADAPTIVE_MAX_K"] = str(serving_cfg.get("max_k", 4))
|
|
133
|
+
env["ADAPTIVE_MIN_BATCH_K"] = "0"
|
|
134
|
+
env["ADAPTIVE_PER_REQUEST"] = "1"
|
|
135
|
+
return env
|
|
136
|
+
|
|
137
|
+
def launch(
|
|
138
|
+
self,
|
|
139
|
+
config: Dict[str, Any],
|
|
140
|
+
port: Optional[int] = None,
|
|
141
|
+
background: bool = False,
|
|
142
|
+
pid_file: str = ".flexsds.pid",
|
|
143
|
+
dry_run: bool = False,
|
|
144
|
+
) -> Optional[subprocess.Popen]:
|
|
145
|
+
"""Khởi chạy tiến trình máy chủ cục bộ."""
|
|
146
|
+
effective_port = port if port is not None else config.get("serving", config).get("port", 8000)
|
|
147
|
+
cmd = self.build_command(config, port=effective_port)
|
|
148
|
+
env = self.setup_environment(config)
|
|
149
|
+
|
|
150
|
+
if dry_run:
|
|
151
|
+
print("\n[FlexSDS] Dry-run execution command:")
|
|
152
|
+
print(" ".join(cmd))
|
|
153
|
+
print(f"Environment: ADAPTIVE_ENABLE_VRAM_GUARD={env.get('ADAPTIVE_ENABLE_VRAM_GUARD')}")
|
|
154
|
+
return None
|
|
155
|
+
|
|
156
|
+
if not self.check_port_available(effective_port):
|
|
157
|
+
print(f"\n\033[31m[Lỗi]\033[0m Cổng {effective_port} đang bị chiếm dụng bởi tiến trình khác.")
|
|
158
|
+
print(f" Gợi ý: Dùng cờ --port <cổng_khác> hoặc chạy: flexsds stop")
|
|
159
|
+
return None
|
|
160
|
+
|
|
161
|
+
if background and os.name == "nt":
|
|
162
|
+
print("Background process-group serving is unsupported on Windows; use foreground serving.")
|
|
163
|
+
return None
|
|
164
|
+
|
|
165
|
+
try:
|
|
166
|
+
if background:
|
|
167
|
+
log_path = "flexsds_server.log"
|
|
168
|
+
log_file = open(log_path, "a", encoding="utf-8")
|
|
169
|
+
proc = subprocess.Popen(
|
|
170
|
+
cmd,
|
|
171
|
+
env=env,
|
|
172
|
+
preexec_fn=os.setsid,
|
|
173
|
+
stdout=log_file,
|
|
174
|
+
stderr=subprocess.STDOUT,
|
|
175
|
+
)
|
|
176
|
+
with open(pid_file, "w", encoding="utf-8") as f:
|
|
177
|
+
f.write(f"{proc.pid}\n")
|
|
178
|
+
print(f"[FlexSDS] Server started in background (PID: {proc.pid}, Log: {log_path})")
|
|
179
|
+
return proc
|
|
180
|
+
else:
|
|
181
|
+
return subprocess.Popen(cmd, env=env)
|
|
182
|
+
except FileNotFoundError:
|
|
183
|
+
print("\n\033[31m[Error]\033[0m Server binary not found in PATH.")
|
|
184
|
+
return None
|
|
185
|
+
|
|
186
|
+
def stop(self, pid_file: str = ".flexsds.pid") -> bool:
|
|
187
|
+
"""Dừng nhóm tiến trình máy chủ vLLM và dọn dẹp sạch tiến trình con."""
|
|
188
|
+
if os.name == "nt":
|
|
189
|
+
print("Local process-group stop is unsupported on Windows; stop the foreground session.")
|
|
190
|
+
return False
|
|
191
|
+
if not os.path.exists(pid_file):
|
|
192
|
+
return False
|
|
193
|
+
|
|
194
|
+
try:
|
|
195
|
+
with open(pid_file, "r", encoding="utf-8") as f:
|
|
196
|
+
content = f.read().strip()
|
|
197
|
+
if not content:
|
|
198
|
+
return False
|
|
199
|
+
pid = int(content)
|
|
200
|
+
|
|
201
|
+
os.killpg(pid, signal.SIGTERM)
|
|
202
|
+
except (ProcessLookupError, ValueError):
|
|
203
|
+
pass
|
|
204
|
+
finally:
|
|
205
|
+
if os.path.exists(pid_file):
|
|
206
|
+
os.remove(pid_file)
|
|
207
|
+
|
|
208
|
+
return True
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"""Modal HTTP app; configuration is passed by the CLI, never embedded credentials.
|
|
2
|
+
|
|
3
|
+
Development: modal serve. Persistent service: modal deploy.
|
|
4
|
+
The web_server decorator owns startup readiness and endpoint exposure.
|
|
5
|
+
"""
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
import json
|
|
8
|
+
import os
|
|
9
|
+
import subprocess
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
|
|
12
|
+
from flexsds.server.local_runner import LocalServingRunner
|
|
13
|
+
|
|
14
|
+
APP_NAME = "flexsds-serving"
|
|
15
|
+
SERVING_CONFIG = json.loads(os.environ.get("FLEXSDS_SERVING_CONFIG", "{}"))
|
|
16
|
+
PORT = int(SERVING_CONFIG.get("port", 8000))
|
|
17
|
+
GPU = SERVING_CONFIG.get("target_gpu", "L4")
|
|
18
|
+
if GPU not in {"L4", "T4", "A10G"} or not 1 <= PORT <= 65535:
|
|
19
|
+
raise ValueError("Unsupported Modal GPU or serving port")
|
|
20
|
+
PROJECT_ROOT = Path(__file__).resolve().parents[3]
|
|
21
|
+
|
|
22
|
+
try:
|
|
23
|
+
import modal
|
|
24
|
+
except ImportError:
|
|
25
|
+
modal = None
|
|
26
|
+
|
|
27
|
+
if modal is not None:
|
|
28
|
+
app = modal.App(APP_NAME)
|
|
29
|
+
image = (
|
|
30
|
+
modal.Image.debian_slim(python_version="3.11")
|
|
31
|
+
.pip_install("vllm==0.30.0", "fastapi", "psutil")
|
|
32
|
+
.env({"PYTHONPATH": "/root/project:/root/project/src",
|
|
33
|
+
"FLEXSDS_SERVING_CONFIG": json.dumps(SERVING_CONFIG)})
|
|
34
|
+
.add_local_dir(str(PROJECT_ROOT / "src"), remote_path="/root/project/src")
|
|
35
|
+
.add_local_file(str(PROJECT_ROOT / "experiments/scalar_vllm_source_manifest.json"),
|
|
36
|
+
remote_path="/root/project/experiments/scalar_vllm_source_manifest.json")
|
|
37
|
+
)
|
|
38
|
+
volume = modal.Volume.from_name("ptit-model-cache", create_if_missing=True)
|
|
39
|
+
|
|
40
|
+
@app.function(image=image, gpu=GPU,
|
|
41
|
+
volumes={"/root/.cache/huggingface": volume}, timeout=3600)
|
|
42
|
+
@modal.web_server(PORT, startup_timeout=600)
|
|
43
|
+
def serve():
|
|
44
|
+
runner = LocalServingRunner()
|
|
45
|
+
# Do not use the laptop's preflight here: the CLI evaluated the remote budget.
|
|
46
|
+
# vLLM and Modal's web-server startup check remain the runtime authorities.
|
|
47
|
+
subprocess.Popen(runner.build_command(SERVING_CONFIG),
|
|
48
|
+
env=runner.setup_environment(SERVING_CONFIG))
|
|
49
|
+
else:
|
|
50
|
+
app = None
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
"""Modal cloud serving runner for FlexSDS (BYOC, ephemeral, and persistent deploy)."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import os
|
|
5
|
+
import json
|
|
6
|
+
import subprocess
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import Any, Dict, List, Optional
|
|
9
|
+
|
|
10
|
+
try:
|
|
11
|
+
from flexsds.cli.output import print_output as print
|
|
12
|
+
from flexsds.core.credentials import CredentialsManager
|
|
13
|
+
except ModuleNotFoundError:
|
|
14
|
+
from src.flexsds.cli.output import print_output as print
|
|
15
|
+
from src.flexsds.core.credentials import CredentialsManager
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class ModalServingRunner:
|
|
19
|
+
"""Quản lý triển khai FlexSDS Serverless trên nền tảng đám mây Modal.com."""
|
|
20
|
+
|
|
21
|
+
DEFAULT_APP_NAME = "flexsds-serving"
|
|
22
|
+
|
|
23
|
+
def __init__(self, credentials_manager: Optional[CredentialsManager] = None) -> None:
|
|
24
|
+
self.cred_mgr = credentials_manager or CredentialsManager()
|
|
25
|
+
|
|
26
|
+
def check_credentials(self) -> bool:
|
|
27
|
+
"""Kiểm tra thông tin định danh Modal qua CredentialsManager, biến môi trường hoặc ~/.modal.toml."""
|
|
28
|
+
self.cred_mgr.inject_into_environ()
|
|
29
|
+
|
|
30
|
+
token_id = os.environ.get("MODAL_TOKEN_ID") or self.cred_mgr.get_credential("modal", "MODAL_TOKEN_ID")
|
|
31
|
+
token_secret = os.environ.get("MODAL_TOKEN_SECRET") or self.cred_mgr.get_credential("modal", "MODAL_TOKEN_SECRET")
|
|
32
|
+
if token_id and token_secret:
|
|
33
|
+
return True
|
|
34
|
+
|
|
35
|
+
toml_path = os.path.expanduser("~/.modal.toml")
|
|
36
|
+
if os.path.exists(toml_path):
|
|
37
|
+
return True
|
|
38
|
+
|
|
39
|
+
return False
|
|
40
|
+
|
|
41
|
+
def build_modal_command(self, config: Dict[str, Any], deploy: bool = False) -> List[str]:
|
|
42
|
+
"""Tạo danh sách đối số dòng lệnh gọi công cụ CLI modal tới modal_app.py."""
|
|
43
|
+
app_path = str(Path(__file__).parent / "modal_app.py")
|
|
44
|
+
return ["modal", "deploy" if deploy else "serve", app_path]
|
|
45
|
+
|
|
46
|
+
def setup_environment(self, config: Dict[str, Any]) -> Dict[str, str]:
|
|
47
|
+
"""Forward non-secret serving settings for app construction in either mode."""
|
|
48
|
+
cfg = config.get("serving", config)
|
|
49
|
+
keys = ("target_model", "draft_model", "draft_method", "use_heterogeneous_vocab",
|
|
50
|
+
"port", "enable_adaptive", "target_gpu", "max_model_len", "max_num_seqs")
|
|
51
|
+
env = dict(os.environ)
|
|
52
|
+
env["FLEXSDS_SERVING_CONFIG"] = json.dumps({k: cfg[k] for k in keys if k in cfg})
|
|
53
|
+
return env
|
|
54
|
+
|
|
55
|
+
def launch(
|
|
56
|
+
self,
|
|
57
|
+
config: Dict[str, Any],
|
|
58
|
+
deploy: bool = False,
|
|
59
|
+
dry_run: bool = False,
|
|
60
|
+
) -> Optional[subprocess.Popen]:
|
|
61
|
+
"""Khởi chạy ứng dụng Modal serverless."""
|
|
62
|
+
if not dry_run and not self.check_credentials():
|
|
63
|
+
print("\n\033[31m[Lỗi định danh]\033[0m Chưa cấu hình thông tin xác thực Modal.")
|
|
64
|
+
print(" Vui lòng chạy: flexsds auth login --provider modal")
|
|
65
|
+
print(" Hoặc cấu hình qua lệnh: modal setup")
|
|
66
|
+
return None
|
|
67
|
+
|
|
68
|
+
cmd = self.build_modal_command(config, deploy=deploy)
|
|
69
|
+
|
|
70
|
+
if dry_run:
|
|
71
|
+
mode_desc = "Persistent Deploy" if deploy else "Ephemeral Run"
|
|
72
|
+
print(f"\n[FlexSDS Modal] Dry-run command ({mode_desc}):")
|
|
73
|
+
print(" ".join(cmd))
|
|
74
|
+
print(f"Serving configuration: {self.setup_environment(config)['FLEXSDS_SERVING_CONFIG']}")
|
|
75
|
+
return None
|
|
76
|
+
|
|
77
|
+
try:
|
|
78
|
+
proc = subprocess.Popen(cmd, env=self.setup_environment(config))
|
|
79
|
+
return proc
|
|
80
|
+
except FileNotFoundError:
|
|
81
|
+
print("\n\033[31m[Lỗi]\033[0m Không tìm thấy công cụ CLI 'modal'. Cài đặt: pip install modal")
|
|
82
|
+
return None
|
|
83
|
+
|
|
84
|
+
def check_process_status(self, proc: subprocess.Popen) -> tuple[bool, Optional[int]]:
|
|
85
|
+
"""Kiểm tra trạng thái tiến trình Modal; phát hiện sớm nếu ứng dụng dừng bất ngờ."""
|
|
86
|
+
code = proc.poll()
|
|
87
|
+
if code is None:
|
|
88
|
+
return True, None
|
|
89
|
+
return False, code
|
|
90
|
+
|
|
91
|
+
def stop(self, app_name: str = DEFAULT_APP_NAME) -> bool:
|
|
92
|
+
"""Dừng ứng dụng đang chạy trên hạ tầng Modal Cloud."""
|
|
93
|
+
cmd = ["modal", "app", "stop", app_name]
|
|
94
|
+
try:
|
|
95
|
+
res = subprocess.run(cmd, capture_output=True, text=True)
|
|
96
|
+
return res.returncode == 0
|
|
97
|
+
except Exception:
|
|
98
|
+
return False
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""vscode cli.ipynb
|
|
3
|
+
|
|
4
|
+
Automatically generated by Colab.
|
|
5
|
+
|
|
6
|
+
Original file is located at
|
|
7
|
+
https://colab.research.google.com/drive/1PYNYxzD2gBAY6QiMm5a9O_gHod0q5aOC
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import os
|
|
11
|
+
from google.colab import drive
|
|
12
|
+
|
|
13
|
+
# CẤU HÌNH THÔNG TIN
|
|
14
|
+
# Public clone URL; Git handles any configured credentials.
|
|
15
|
+
REPO_OWNER = "theflyingkhui04"
|
|
16
|
+
REPO_NAME = "do-an-ptit-d22"
|
|
17
|
+
TARGET_DIR = f"/root/{REPO_NAME}"
|
|
18
|
+
CLONE_URL = f"https://github.com/{REPO_OWNER}/{REPO_NAME}.git"
|
|
19
|
+
|
|
20
|
+
VSCODE_DRIVE_BACKUP = "/content/drive/MyDrive/vllm_colab_backup"
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
# BƯỚC 1: KẾT NỐI GOOGLE DRIVE
|
|
24
|
+
print("\n" + "="*60)
|
|
25
|
+
print("==> BƯỚC 1/4: KẾT NỐI GOOGLE DRIVE")
|
|
26
|
+
print("="*60)
|
|
27
|
+
drive.mount('/content/drive', force_remount=False)
|
|
28
|
+
os.makedirs(f"{VSCODE_DRIVE_BACKUP}/.vscode-cli", exist_ok=True)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
# BƯỚC 2: ĐỒNG BỘ MÃ NGUỒN REPO
|
|
32
|
+
print("\n" + "="*60)
|
|
33
|
+
print("==> BƯỚC 2/4: ĐỒNG BỘ MÃ NGUỒN REPO TỪ GITHUB")
|
|
34
|
+
print("="*60)
|
|
35
|
+
if not os.path.exists(TARGET_DIR):
|
|
36
|
+
!git clone {CLONE_URL} {TARGET_DIR}
|
|
37
|
+
else:
|
|
38
|
+
!git -C {TARGET_DIR} pull
|
|
39
|
+
|
|
40
|
+
!chmod +x {TARGET_DIR}/scripts/*.sh
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
# BƯỚC 3: CÀI ĐẶT MÔI TRƯỜNG vLLM
|
|
44
|
+
print("\n" + "="*60)
|
|
45
|
+
print("==> BƯỚC 3/4: CÀI ĐẶT vLLM TRÊN SSD COLAB")
|
|
46
|
+
print("="*60)
|
|
47
|
+
!pip install -r {TARGET_DIR}/requirements-server.txt
|
|
48
|
+
|
|
49
|
+
print("\n--> Đang gỡ bỏ torchaudio để tránh xung đột CUDA...")
|
|
50
|
+
!pip uninstall -y torchaudio
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
# BƯỚC 4: THIẾT LẬP VS CODE CLI & KHỞI CHẠY TUNNEL
|
|
55
|
+
print("\n" + "="*60)
|
|
56
|
+
print("==> BƯỚC 4/4: KHỞI TẠO VS CODE TUNNEL 'colab-t4'")
|
|
57
|
+
print("="*60)
|
|
58
|
+
|
|
59
|
+
# 1. Tải CLI nếu chưa có (Sử dụng Direct Link của Microsoft để tránh lỗi DNS/Redirect trên T4 GPU)
|
|
60
|
+
if os.path.exists("./code"):
|
|
61
|
+
print("--> Found existing VS Code CLI, removing for fresh install...")
|
|
62
|
+
!rm -rf ./code
|
|
63
|
+
if os.path.exists("vscode_cli.tar.gz"):
|
|
64
|
+
print("--> Found existing vscode_cli.tar.gz, removing for fresh download...")
|
|
65
|
+
!rm -f vscode_cli.tar.gz
|
|
66
|
+
|
|
67
|
+
print("--> Downloading and extracting VS Code CLI (Direct Link)...")
|
|
68
|
+
!wget -O vscode_cli.tar.gz "https://update.code.visualstudio.com/latest/cli-alpine-x64/stable" && tar -xf vscode_cli.tar.gz
|
|
69
|
+
|
|
70
|
+
# 2. Liên kết thư mục lưu trữ thông tin đăng nhập với Drive
|
|
71
|
+
os.makedirs(f"{VSCODE_DRIVE_BACKUP}/.vscode-cli", exist_ok=True)
|
|
72
|
+
!ln -sfn {VSCODE_DRIVE_BACKUP}/.vscode-cli /root/.vscode-cli
|
|
73
|
+
|
|
74
|
+
# 3. Đăng nhập với cờ cố định là GitHub
|
|
75
|
+
print("--> Kiểm tra đăng nhập GitHub:")
|
|
76
|
+
!./code tunnel user login --provider github
|
|
77
|
+
|
|
78
|
+
# 4. Bật Tunnel
|
|
79
|
+
print("\n" + "="*60)
|
|
80
|
+
print(" ĐANG KHỞI CHẠY TUNNEL 'colab-t4'...")
|
|
81
|
+
print("="*60 + "\n")
|
|
82
|
+
!./code tunnel --accept-server-license-terms --name colab-t4
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: flexsds
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Flexible Speculative Decoding Serving Engine for LLMs
|
|
5
|
+
Author: Can Duc Khoi, Bui Duc Dai, Nguyen The Duong
|
|
6
|
+
Requires-Python: >=3.9
|
|
7
|
+
Description-Content-Type: text/markdown
|
|
8
|
+
Requires-Dist: psutil
|
|
9
|
+
Provides-Extra: dev
|
|
10
|
+
Requires-Dist: pytest>=8.0; extra == "dev"
|
|
11
|
+
|
|
12
|
+
<div align="center">
|
|
13
|
+
|
|
14
|
+
<img src="logo/flexsds_banner.gif" alt="PTIT - FlexSDS Banner" width="100%" />
|
|
15
|
+
|
|
16
|
+
# FlexSDS: Hệ thống phục vụ suy luận phỏng đoán thích ứng nhận biết phần cứng
|
|
17
|
+
|
|
18
|
+
**Bộ công cụ phục vụ suy luận phỏng đoán thông minh cho các mô hình ngôn ngữ lớn tự lưu trữ**
|
|
19
|
+
*Hardware-Aware Adaptive Speculative Decoding Serving Toolkit for Self-Hosted LLMs*
|
|
20
|
+
|
|
21
|
+
[](https://portal.ptit.edu.vn/)
|
|
22
|
+
[](https://python.org)
|
|
23
|
+
[](https://github.com/vllm-project/vllm)
|
|
24
|
+
[](https://modal.com)
|
|
25
|
+
[]()
|
|
26
|
+
|
|
27
|
+
<br/>
|
|
28
|
+
|
|
29
|
+
<picture>
|
|
30
|
+
<source media="(prefers-color-scheme: dark)" srcset="logo/icon_dark.webp">
|
|
31
|
+
<source media="(prefers-color-scheme: light)" srcset="logo/icon_white.webp">
|
|
32
|
+
<img alt="FlexSDS Logo" src="logo/icon_dark.webp" width="90" height="90">
|
|
33
|
+
</picture>
|
|
34
|
+
|
|
35
|
+
</div>
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## 1. Tổng quan đề tài
|
|
40
|
+
|
|
41
|
+
Trong các hệ thống xử lý tài liệu tự động và chuỗi tác vụ đa bước (multi-step LLM pipeline), **độ trễ sinh từ trong pha giải mã (Decode Latency)** là nút thắt cổ chai lớn nhất và bị tích lũy lũy tiến qua từng lời gọi mô hình. Kỹ thuật suy luận phỏng đoán truyền thống (Static Speculative Decoding) sử dụng cấu hình nháp cố định thường gặp phải hai rào cản nghiêm trọng trong môi trường triển khai thực tế:
|
|
42
|
+
1. **Hiện tượng tăng tốc âm (Negative Speedup):** Khi gặp các tác vụ suy luận tự do có entropy cao hoặc khi hệ thống chịu tải đồng thời lớn ($c \ge 16$), việc ép chạy mô hình nháp vô ích biến nó thành tải ký sinh, làm giảm tốc độ thực tế (chỉ đạt $0.67\times - 0.81\times$ so với suy luận tuần tự cơ sở).
|
|
43
|
+
2. **Nguy cơ tràn bộ nhớ (Out-Of-Memory - OOM):** Việc đồng thời nạp cả mô hình đích, mô hình nháp và phân bổ bộ đệm KV dễ dẫn đến tràn VRAM trên các dòng GPU phổ thông (NVIDIA Tesla T4 16GB, RTX 4090 24GB).
|
|
44
|
+
|
|
45
|
+
**FlexSDS** (*Flexible Speculative Decoding Serving*) là giải pháp mã nguồn mở hoàn chỉnh do nhóm sinh viên PTIT nghiên cứu và phát triển. Hệ thống tích hợp lớp điều khiển thích ứng **TreeAdaptive**, tự động nhận diện phần cứng, nạp ma trận chi phí thực nghiệm và giải ngưỡng hòa vốn động $\alpha^*(N, B)$ theo thời gian thực để co giãn cấu trúc cây nháp hoặc ngắt về suy luận tuần tự cơ sở, **bảo đảm tăng tốc tối đa nhưng không bao giờ bị chậm hơn chế độ mặc định (Lossless Guarantee)**.
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
## 2. Ứng dụng thực nghiệm: Luồng đa tác vụ trên hóa đơn bán hàng
|
|
50
|
+
|
|
51
|
+
Để kiểm chứng năng lực thích ứng trong môi trường thực tế, FlexSDS được triển khai phục vụ quy trình xử lý và phân tích hóa đơn bán hàng tiếng Việt (bộ dữ liệu MC-OCR) với 3 tác vụ đại diện:
|
|
52
|
+
|
|
53
|
+
```text
|
|
54
|
+
┌────────────────────────────────────────────────────────────────────────┐
|
|
55
|
+
│ HỆ THỐNG ĐA TÁC VỤ TRÊN HÓA ĐƠN BÁN HÀNG │
|
|
56
|
+
├────────────────────────────────────────────────────────────────────────┤
|
|
57
|
+
│ [Ảnh / Thư mục hóa đơn tải lên] │
|
|
58
|
+
│ │ │
|
|
59
|
+
│ ▼ │
|
|
60
|
+
│ ┌────────────────────────────────────────────────────────────────────┐ │
|
|
61
|
+
│ │ TÁC VỤ 1: TRÍCH XUẤT NỘI DUNG HÓA ĐƠN THÀNH JSON (MC-OCR W1) │ │
|
|
62
|
+
│ │ • Trích xuất 7 trường pháp lý: Người bán, MST, Ngày, Tiền, VAT... │ │
|
|
63
|
+
│ │ • Cú pháp JSON cố định, alpha > 90% -> Tăng tốc tối đa (1.35x-1.4x) │ │
|
|
64
|
+
│ └──────────────────────────────────┬─────────────────────────────────┘ │
|
|
65
|
+
│ │ │
|
|
66
|
+
│ ▼ │
|
|
67
|
+
│ ┌────────────────────────────────────────────────────────────────────┐ │
|
|
68
|
+
│ │ TÁC VỤ 2: SINH MÃ SQL TRUY VẤN DỮ LIỆU HÓA ĐƠN (TEXT-TO-SQL W2) │ │
|
|
69
|
+
│ │ • Chuyển câu hỏi quản trị kinh doanh tiếng Việt sang truy vấn SQL │ │
|
|
70
|
+
│ │ • Cú pháp SQL bán cấu trúc, alpha ~ 80% -> Tăng tốc ổn định 1.3x │ │
|
|
71
|
+
│ └──────────────────────────────────┬─────────────────────────────────┘ │
|
|
72
|
+
│ │ │
|
|
73
|
+
│ ▼ │
|
|
74
|
+
│ ┌────────────────────────────────────────────────────────────────────┐ │
|
|
75
|
+
│ │ TÁC VỤ 3: HỎI ĐÁP & PHÂN TÍCH TÀI CHÍNH THÔNG MINH (INVOICE CHAT) │ │
|
|
76
|
+
│ │ • Khung chat tương tác trực tiếp về các hóa đơn vừa xử lý │ │
|
|
77
|
+
│ │ • Văn bản tự do, alpha < 60% -> Tự ngắt nháp (k=0), tránh trễ âm │ │
|
|
78
|
+
│ └────────────────────────────────────────────────────────────────────┘ │
|
|
79
|
+
└────────────────────────────────────────────────────────────────────────┘
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
---
|
|
83
|
+
|
|
84
|
+
## 3. Các đột phá kỹ thuật nổi bật
|
|
85
|
+
|
|
86
|
+
* **Lớp thích ứng nhận biết phần cứng (Hardware-Aware TreeAdaptive v0.2):** Tự động nhận diện kiến trúc GPU (`sm_89` NVIDIA L4, `sm_75` Tesla T4) và giải nghiệm phương trình hòa vốn theo thời gian thực:
|
|
87
|
+
$$\alpha^*(N, B) = \arg_\alpha \left( \frac{\mathbb{E}[\tau(N, \alpha)]}{C(N, B)} = \frac{1}{C(0, B)} \right)$$
|
|
88
|
+
* **Triệt tiêu hoàn toàn hiện tượng tăng tốc âm:** Khi tỷ lệ chấp nhận thực tế $\alpha < \alpha^*$ hoặc khi bộ đệm KV vượt ngưỡng $90\%$, hệ thống lập tức ngắt suy đoán về chế độ an toàn ($N=0$), bảo đảm tốc độ luôn $\ge 1.00\times$.
|
|
89
|
+
* **Bộ gác cổng an toàn tương thích (Pre-flight Guard):** Quét dung lượng VRAM thực tế, kiểm tra độ lệch từ vựng (Heterogeneous Vocabulary) giữa mô hình đích và mô hình nháp trước khi khởi động tiến trình serving.
|
|
90
|
+
* **Đa nền tảng phục vụ:** Hỗ trợ khởi chạy server vLLM cục bộ (Local Dedicated Runner) hoặc triển khai tức thì lên nền tảng đám mây không máy chủ (Modal Cloud GPU L4) với chuẩn giao tiếp OpenAI API `/v1/chat/completions`.
|
|
91
|
+
* **Giao diện dòng lệnh tương tác (Interactive CLI Wizard):** Menu hỏi–đáp từng bước có nhận diện thương hiệu PTIT bằng ký tự khối Unicode và mã màu ANSI TrueColor 24-bit.
|
|
92
|
+
|
|
93
|
+
---
|
|
94
|
+
|
|
95
|
+
## 4. Bốn chế độ suy luận đối chứng (4 Serving Lanes)
|
|
96
|
+
|
|
97
|
+
Hệ thống cung cấp cơ chế đo kiểm thực nghiệm chuẩn hóa giữa 4 làn phục vụ trên cùng một hạ tầng phần cứng:
|
|
98
|
+
|
|
99
|
+
1. **Làn 1 (`vanilla`):** Giải mã tuần tự cơ sở của mô hình đích Qwen2.5-7B-Instruct (chuẩn đối chứng $1.00\times$).
|
|
100
|
+
2. **Làn 2 (`static`):** Suy luận phỏng đoán tĩnh với mô hình nháp độc lập Qwen2.5-0.5B-Instruct ($k=3$ cố định).
|
|
101
|
+
3. **Làn 3 (`adaptive`):** Suy luận phỏng đoán thích ứng động TreeAdaptive co giãn ngân sách nháp theo tải và $\alpha$.
|
|
102
|
+
4. **Làn 4 (`eagle`):** Đầu nháp suy đoán chuyên biệt EAGLE-3.1 tự huấn luyện cho miền hóa đơn tiếng Việt (`duckhoi/qwen2.5-7b-eagle3.1-speculator`).
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
## 5. Hướng dẫn cài đặt và sử dụng nhanh
|
|
107
|
+
|
|
108
|
+
### 5.1. Cài đặt package
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
# Clone kho mã nguồn từ GitHub
|
|
112
|
+
git clone https://github.com/theflyingkhui04/do-an-ptit-d22.git
|
|
113
|
+
cd do-an-ptit-d22
|
|
114
|
+
|
|
115
|
+
# Cài đặt môi trường ở chế độ phát triển (Editable Mode)
|
|
116
|
+
pip install -e .
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### 5.2. Khởi chạy giao diện dòng lệnh tương tác (Interactive Wizard)
|
|
120
|
+
|
|
121
|
+
Chỉ cần gõ lệnh `flexsds` trên terminal để khởi động trợ lý hướng dẫn từng bước:
|
|
122
|
+
|
|
123
|
+
```bash
|
|
124
|
+
flexsds
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
Trợ lý sẽ tự động:
|
|
128
|
+
1. Quét thông số phần cứng GPU và hiển thị banner nhận diện thương hiệu PTIT.
|
|
129
|
+
2. Hướng dẫn chọn tác vụ, mô hình đích (Target), mô hình nháp (Draft).
|
|
130
|
+
3. Chạy Pre-flight Guard kiểm tra an toàn VRAM và xung đột bảng từ vựng.
|
|
131
|
+
4. Tự động nạp ma trận chi phí thực nghiệm và khởi chạy server phục vụ.
|
|
132
|
+
|
|
133
|
+
### 5.3. Sử dụng lệnh trực tiếp (Direct CLI Commands)
|
|
134
|
+
|
|
135
|
+
```bash
|
|
136
|
+
# Quét nhanh phần cứng và trạng thái VRAM
|
|
137
|
+
flexsds detect
|
|
138
|
+
|
|
139
|
+
# Khởi chạy endpoint serving trên nền tảng Modal Cloud
|
|
140
|
+
flexsds serve --backend modal --target Qwen/Qwen2.5-7B-Instruct-AWQ --draft Qwen/Qwen2.5-0.5B-Instruct
|
|
141
|
+
|
|
142
|
+
# Khởi chạy server phục vụ cục bộ trên GPU máy chủ
|
|
143
|
+
flexsds serve --backend local --port 8000 --gpu-memory-utilization 0.90
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
---
|
|
147
|
+
|
|
148
|
+
## 6. Web demo và bảng đo lường metrics đối chứng
|
|
149
|
+
|
|
150
|
+
Hệ thống tích hợp giao diện web demo trực quan hỗ trợ:
|
|
151
|
+
* Tải lên một hoặc nhiều ảnh hóa đơn (kéo thả cả thư mục hoặc chụp ảnh trực tiếp).
|
|
152
|
+
* Lựa chọn 3 tác vụ xử lý độc lập: Trích xuất JSON, Sinh câu truy vấn SQL, hoặc Chat hỏi đáp tài chính.
|
|
153
|
+
* **Thiết lập đa endpoint song song:** Cho phép thêm URL của nhiều server (Vanilla vs. Static vs. FlexSDS Adaptive) để chạy đồng thời cùng một dữ liệu đầu vào.
|
|
154
|
+
* **Bảng so sánh metrics thời gian thực:**
|
|
155
|
+
* **TPOT (Time per Output Token - ms/token):** Đo tốc độ sinh từ thực tế của từng endpoint.
|
|
156
|
+
* **Throughput (tokens/s):** Tốc độ xả token ra màn hình.
|
|
157
|
+
* **Tỉ lệ chấp nhận nháp $\alpha$ (%):** Phản ánh đặc trưng ngữ nghĩa của tác vụ.
|
|
158
|
+
* **Trạng thái thích ứng:** Huy hiệu hiển thị quyết định co giãn cây của FlexSDS (`[Boost: k=4]`, `[Balanced: k=2]`, hoặc `[Fallback: k=0]`).
|
|
159
|
+
* **Hệ số tăng tốc (Speedup vs. Vanilla):** Trực quan hóa hiện tượng tăng tốc âm của chế độ tĩnh và khả năng duy trì tốc độ an toàn của FlexSDS.
|
|
160
|
+
|
|
161
|
+
---
|
|
162
|
+
|
|
163
|
+
## 7. Cấu trúc kho mã nguồn
|
|
164
|
+
|
|
165
|
+
```text
|
|
166
|
+
do-an-ptit-d22/
|
|
167
|
+
├── logo/ # Tài nguyên hình ảnh, logo PTIT và banner GIF
|
|
168
|
+
│ ├── flexsds_banner.gif # Banner động độ phân giải cao hiển thị trên README
|
|
169
|
+
│ ├── flexsds_banner_static.webp # Banner tĩnh
|
|
170
|
+
│ ├── icon_dark.webp # Logo biểu tượng chế độ nền tối (Dark mode)
|
|
171
|
+
│ └── icon_white.webp # Logo biểu tượng chế độ nền sáng (Light mode)
|
|
172
|
+
├── pyproject.toml # Cấu hình package Python và entrypoint dòng lệnh flexsds
|
|
173
|
+
├── src/
|
|
174
|
+
│ ├── flexsds/ # Gói mã nguồn công cụ FlexSDS
|
|
175
|
+
│ │ ├── cli/ # Giao diện CLI, Interactive Wizard và banner ANSI
|
|
176
|
+
│ │ ├── core/ # Bộ quét phần cứng, bộ gác cổng an toàn & TreeAdaptive
|
|
177
|
+
│ │ ├── profiles/ # Danh mục ma trận chi phí thực nghiệm (GPU L4 & T4)
|
|
178
|
+
│ │ └── server/ # Bộ điều phối runner (Modal serverless & Local vLLM)
|
|
179
|
+
│ ├── server/ # Bộ lập lịch thích ứng vLLM AdaptiveAsyncScheduler
|
|
180
|
+
│ ├── proposer/ # Bộ điều khiển nháp và mô hình chi phí thực nghiệm
|
|
181
|
+
│ └── pipeline/ # Đường ống xử lý hóa đơn MC-OCR (JSON, SQL, Report)
|
|
182
|
+
├── experiments/ # Bộ công cụ đo benchmark 4 làn và kịch bản thực nghiệm
|
|
183
|
+
├── docs/ # Tài liệu kỹ thuật, quy định thiết kế và outline báo cáo
|
|
184
|
+
└── tests/ # Bộ kiểm thử tự động toàn diện (unit & regression tests)
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
---
|
|
188
|
+
|
|
189
|
+
## 8. Đội ngũ thực hiện
|
|
190
|
+
|
|
191
|
+
Nhóm áp dụng mô hình **Ghép cặp Luân phiên 70/30 (Pair-Rotation System)**: Mỗi thành viên chủ trì (Lead) 70% mảng chuyên môn cốt lõi, đồng thời luân phiên đồng hành (Partner) 30% ở các mảng còn lại:
|
|
192
|
+
|
|
193
|
+
| Chuyên môn cốt lõi (Lead 70%) | Thành viên thực hiện | Vai trò trong dự án |
|
|
194
|
+
| :--- | :--- | :--- |
|
|
195
|
+
| **AI Systems & Core-Engine** | **Cấn Đức Khôi** ([@theflyingkhui04](https://github.com/theflyingkhui04)) | Lead Architecture & Core vLLM Scheduler |
|
|
196
|
+
| **MLOps & Infrastructure** | **Bùi Đức Đại** | Lead Benchmark Harness & GPU Telemetry |
|
|
197
|
+
| **AI Application & Data** | **Dương Nguyễn Thế** | Lead Pipeline MC-OCR & Invoices Analytics |
|
|
198
|
+
| **Giảng viên hướng dẫn** | *(Cập nhật theo quyết định của khoa)* | Cố vấn khoa học & Giám sát chất lượng |
|
|
199
|
+
|
|
200
|
+
---
|
|
201
|
+
|
|
202
|
+
## 9. Bản quyền
|
|
203
|
+
|
|
204
|
+
Dự án được phát triển phục vụ đồ án tốt nghiệp đại học tại **Học viện Công nghệ Bưu chính Viễn thông (PTIT)**, niên khóa 2022–2026. Mã nguồn được phát hành theo giấy phép mã nguồn mở Apache 2.0.
|