Skip to content

Commit eb2b12c

Browse files
Add git commit and unify flagos log (#102)
1 parent 95207b6 commit eb2b12c

6 files changed

Lines changed: 186 additions & 1 deletion

File tree

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
__pycache__/
33
build/
44
dist/
5+
vllm_fl/_version.py
56

67
# Coverage
78
.coverage

pyproject.toml

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ build-backend = "setuptools.build_meta"
1313

1414
[project]
1515
name = "vllm-plugin-fl"
16-
version = "0.1.0"
16+
dynamic = ["version"]
1717
description = "A vLLM plugin built on the FlagOS unified multi-chip backend"
1818
license = "Apache-2.0"
1919
license-files = ["LICENSE"]
@@ -56,6 +56,40 @@ test = [
5656
"modelscope>=1.18.1",
5757
]
5858

59+
[tool.setuptools_scm]
60+
# Generate a static `_version.py` at build time so the installed package
61+
# never needs to run `git` at import time (same pattern as vLLM/PyTorch).
62+
# The template extracts commit sha and date from the version string and
63+
# exposes them as `git_version`, `git_date`, and `git_info`.
64+
write_to = "vllm_fl/_version.py"
65+
# Keep the public version at the nearest tag without appending `.devN`.
66+
version_scheme = "only-version"
67+
write_to_template = '''# This file is generated by setuptools-scm at build time. Do not edit.
68+
# At runtime vllm_fl/version.py imports git_version and git_date from here.
69+
from __future__ import annotations
70+
import re as _re
71+
72+
__version__: str = "{version}"
73+
version: str = __version__
74+
_node_date: str = "{scm_version.node_date}"
75+
76+
_sha_m = _re.search(r"(?:^|[.+-])g(?P<sha>[0-9a-f]{{7,40}})(?:[.+-]|$)", __version__)
77+
_date_m = _re.search(r"(?:^|[.+-])d(?P<ymd>\d{{8}})(?:[.+-]|$)", __version__)
78+
79+
git_version: str = _sha_m.group("sha") if _sha_m else "Unknown"
80+
git_date: str = (
81+
_node_date if _node_date not in ("", "None")
82+
else (
83+
_date_m.group("ymd")[0:4] + "-" + _date_m.group("ymd")[4:6] + "-" + _date_m.group("ymd")[6:8]
84+
if _date_m else "Unknown"
85+
)
86+
)
87+
git_info: dict[str, str] = {{"id": git_version, "date": git_date}}
88+
'''
89+
# Include commit date in the local version string so the template can parse it.
90+
local_scheme = "node-and-date"
91+
fallback_version = "0.0.0"
92+
5993
[tool.setuptools.packages.find]
6094
where = ["."]
6195
include = ["vllm_fl*"]

tests/unit_tests/test_version.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import vllm_fl.version as version
2+
3+
4+
def test_public_git_exports_shape():
5+
assert isinstance(version.git_version, str)
6+
assert isinstance(version.git_info, dict)
7+
assert set(version.git_info.keys()) == {"id", "date"}

vllm_fl/__init__.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,11 @@
22

33
import os
44
import logging
5+
56
from vllm_fl.utils import get_op_config as _get_op_config
67

8+
from . import version as version # PyTorch-style: vllm_fl.version.git_version
9+
710

811
logger = logging.getLogger(__name__)
912

vllm_fl/dispatch/manager.py

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,48 @@
2929
# Debug printing control
3030
_DISPATCH_DEBUG = os.getenv("VLLM_FL_DISPATCH_DEBUG", "0") == "1"
3131

32+
# Record which dispatch-level ops are used into the FlagGems oplist file,
33+
# so users can inspect runtime op usage in one place.
34+
_FLAGOS_OPLIST_LOCK = threading.Lock()
35+
_RECORDED_FLAGOS_OPS: Set[Tuple[str, str]] = set() # (op_name, impl_id)
36+
37+
38+
def _record_default_flagos_op(op_name: str, impl: OpImpl) -> None:
39+
"""Record dispatch-level op usage into the FlagGems oplist file.
40+
41+
Writes through the FlagGems logger's file handlers directly so that the
42+
record goes via the same file descriptor that FlagGems itself uses. This
43+
avoids a file-position race between two independent file descriptors (the
44+
old ``open(path, "a+")`` approach vs FlagGems' ``FileHandler(mode="w")``)
45+
that caused dispatch entries to be silently overwritten in short-lived
46+
processes such as offline inference.
47+
"""
48+
key = (op_name, impl.impl_id)
49+
with _FLAGOS_OPLIST_LOCK:
50+
if key in _RECORDED_FLAGOS_OPS:
51+
return
52+
try:
53+
fg_logger = logging.getLogger("flag_gems")
54+
line = (
55+
f"[DEBUG] vllm_fl.dispatch.ops.{op_name}: {impl.impl_id}"
56+
)
57+
# Write directly through each FlagGems-owned FileHandler so
58+
# that the file position stays synchronised with FlagGems'
59+
# own writes. Using ``logger.debug()`` would prepend an
60+
# unwanted ``[DEBUG] flag_gems.<funcName>:`` prefix added by
61+
# the handler's formatter.
62+
for handler in fg_logger.handlers:
63+
if (
64+
isinstance(handler, logging.FileHandler)
65+
and getattr(handler, "_flaggems_owned", False)
66+
):
67+
handler.stream.write(line + "\n")
68+
handler.stream.flush()
69+
_RECORDED_FLAGOS_OPS.add(key)
70+
except Exception:
71+
# Never break inference/serving due to diagnostics I/O.
72+
return
73+
3274

3375
@dataclass
3476
class _OpManagerState:
@@ -485,6 +527,8 @@ def call(self, op_name: str, *args, **kwargs):
485527
f"Op '{op_name}' switched from '{last_impl_id}' to '{impl_id}' "
486528
f"(kind={impl.kind.value}, vendor={impl.vendor})"
487529
)
530+
if impl.kind == BackendImplKind.DEFAULT:
531+
_record_default_flagos_op(op_name, impl)
488532
break
489533
self._called_ops[op_name] = impl_id
490534

@@ -526,6 +570,8 @@ def call(self, op_name: str, *args, **kwargs):
526570
f"Op '{op_name}' switched from '{last_impl_id}' to '{impl.impl_id}' "
527571
f"(kind={impl.kind.value}, vendor={impl.vendor})"
528572
)
573+
if impl.kind == BackendImplKind.DEFAULT:
574+
_record_default_flagos_op(op_name, impl)
529575
self._called_ops[op_name] = impl.impl_id
530576
else:
531577
# Always log fallback attempts (these are important runtime events)
@@ -540,6 +586,8 @@ def call(self, op_name: str, *args, **kwargs):
540586
if idx > 0:
541587
with self._lock:
542588
self._called_ops[op_name] = impl.impl_id
589+
if impl.kind == BackendImplKind.DEFAULT:
590+
_record_default_flagos_op(op_name, impl)
543591

544592
return result
545593

vllm_fl/version.py

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
"""vllm_fl version information (PyTorch-style).
2+
3+
This module is intentionally lightweight and safe to import.
4+
5+
At build time (pip install .), setuptools-scm writes `vllm_fl/_version.py`
6+
with pre-computed `git_version` and `git_date`. At runtime we simply import
7+
that static file, exactly as vLLM does, so no subprocess is needed in the
8+
happy path. Subprocess git calls are only used as a last-resort fallback
9+
(e.g. pip install -e . or running from source without a build step).
10+
"""
11+
12+
from __future__ import annotations
13+
14+
import os
15+
import subprocess
16+
from importlib import metadata
17+
18+
19+
def _pkg_version() -> str:
20+
# Project name in pyproject.toml is "vllm-plugin-fl".
21+
try:
22+
return metadata.version("vllm-plugin-fl")
23+
except Exception:
24+
pass
25+
try:
26+
from . import _version
27+
return _version.__version__
28+
except Exception:
29+
return "0.0.0+unknown"
30+
31+
32+
def _git_head_from_repo() -> str | None:
33+
# Only works from a git checkout with `git` available.
34+
try:
35+
root = os.path.dirname(os.path.dirname(__file__))
36+
return subprocess.check_output(
37+
["git", "-C", root, "rev-parse", "HEAD"],
38+
stderr=subprocess.DEVNULL,
39+
text=True,
40+
timeout=0.2,
41+
).strip() or None
42+
except Exception:
43+
return None
44+
45+
46+
def _git_commit_date_from_repo() -> str | None:
47+
# Returns YYYY-MM-DD of the HEAD committer date.
48+
try:
49+
root = os.path.dirname(os.path.dirname(__file__))
50+
out = subprocess.check_output(
51+
["git", "-C", root, "show", "-s", "--format=%cI", "HEAD"],
52+
stderr=subprocess.DEVNULL,
53+
text=True,
54+
timeout=0.2,
55+
).strip()
56+
return out[:10] if out else None
57+
except Exception:
58+
return None
59+
60+
61+
def _load_scm() -> tuple[str | None, str | None]:
62+
"""Read (commit_id, commit_date) from the build-time generated _version.py.
63+
64+
setuptools-scm writes `vllm_fl/_version.py` at `pip install .` time with
65+
pre-computed `git_version` (commit sha) and `git_date` (YYYY-MM-DD)
66+
derived from the version string. Importing that static file avoids any
67+
runtime git subprocess in the common installed case.
68+
"""
69+
try:
70+
from . import _version as _v
71+
except Exception:
72+
return None, None
73+
cid = getattr(_v, "git_version", None)
74+
cdate = getattr(_v, "git_date", None)
75+
cid = cid if isinstance(cid, str) and cid not in ("", "Unknown") else None
76+
cdate = cdate if isinstance(cdate, str) and cdate not in ("", "Unknown") else None
77+
return cid, cdate
78+
79+
80+
__version__ = _pkg_version()
81+
82+
_scm_id, _scm_date = _load_scm()
83+
84+
# Public: git commit id of the installed package/build (best-effort).
85+
git_version: str = _scm_id or _git_head_from_repo() or "Unknown"
86+
87+
# Public: git metadata aligned with torch.version.git_info style.
88+
git_info: dict[str, str] = {
89+
"id": git_version,
90+
"date": _scm_date or _git_commit_date_from_repo() or "Unknown",
91+
}
92+

0 commit comments

Comments
 (0)