From 23909c205e06f0832ecb4344066d7c52bf5ec7be Mon Sep 17 00:00:00 2001 From: SZCJW <792430652@qq.com> Date: Sun, 29 Mar 2026 05:47:30 +0800 Subject: [PATCH] x --- README.md | 13 ++++++++----- app/config.py | 4 ++++ app/model_catalog.py | 28 +++++++++++++++++++++++++++- config.json | 6 ++++++ docker-compose.yml | 2 +- 5 files changed, 46 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 1a07107..edfb933 100644 --- a/README.md +++ b/README.md @@ -61,6 +61,8 @@ - `api_key`:接口访问密钥(同时用于 8000 与 8001) - `tensor_parallel_size`:张量并行数,双卡建议 `2` - `dtype`:推理精度,默认 `bfloat16` +- `model_root`:本地模型根目录,建议 `/opt/model` +- `offline_mode`:保留字段,当前实现固定只走离线本地模型 - `models.selected`:当前生效模型,留空时回退到 `models.default` ## config.json 说明 @@ -70,7 +72,7 @@ - `models.default`:默认模型名 - `models.selected`:当前生效模型名 - `models.profiles`:模型配置集合 -- 每个模型至少建议包含:`hf_model_id`、`ctx`、`max_num_seqs`、`max_tokens`、`gpu_util`、`valid_tp` +- 每个模型必须包含:`local_path`、`ctx`、`max_num_seqs`、`max_tokens`、`gpu_util`、`valid_tp` 启动时会按以下优先级选模型: @@ -79,7 +81,7 @@ 模型被选中后,会自动覆盖运行参数,包括: -- `model_name` ← `hf_model_id` +- `model_name` ← `local_path`(相对路径会自动拼接 `model_root`) - `max_model_len` ← `ctx` - `max_num_seqs` ← `max_num_seqs` - `max_tokens` ← `max_tokens` @@ -100,19 +102,19 @@ docker compose up -d --build 3. 验证自定义推理服务: ```bash -curl http://localhost:8000/health +curl http://localhost:/health ``` 4. 验证 OpenAI 协议服务: ```bash -curl http://localhost:8001/v1/models +curl http://localhost:/v1/models ``` ## 推理请求示例 ```bash -curl -X POST "http://localhost:8000/v1/generate" \ +curl -X POST "http://localhost:/v1/generate" \ -H "Content-Type: application/json" \ -d "{\"prompt\":\"请用三句话介绍大模型推理优化\",\"max_tokens\":128,\"temperature\":0.7}" ``` @@ -132,6 +134,7 @@ curl -X POST "http://localhost:8001/v1/chat/completions" \ - API Key 使用 `config.json` 中 `api_key` - 模型名使用 `config.json` 中 `models.profiles.<模型名>.served_model_name` - 若使用工具调用,`config.json` 中应配置 `tool_call_parser` 与 `enable_auto_tool_choice` +- 服务强制离线模式,不会回退到 Hugging Face 远程下载 - 所有路径按 Ubuntu 规范填写,本地模型建议使用 `/opt/model/<模型目录>` ## 双 AMD R9700 调优建议 diff --git a/app/config.py b/app/config.py index 9ddf929..821d65e 100644 --- a/app/config.py +++ b/app/config.py @@ -17,6 +17,8 @@ class Settings(BaseModel): port: int = 8000 openai_host: str = "0.0.0.0" openai_port: int = 8001 + model_root: str = "/opt/model" + offline_mode: bool = True max_model_len: int = 8192 gpu_memory_utilization: float = 0.92 tensor_parallel_size: int = 2 @@ -42,6 +44,8 @@ def get_settings() -> Settings: port=runtime["port"], openai_host=runtime["openai_host"], openai_port=runtime["openai_port"], + model_root=runtime["model_root"], + offline_mode=runtime["offline_mode"], api_key=runtime["api_key"], tensor_parallel_size=runtime["tensor_parallel_size"], dtype=runtime["dtype"], diff --git a/app/model_catalog.py b/app/model_catalog.py index 72d1864..b4097fc 100644 --- a/app/model_catalog.py +++ b/app/model_catalog.py @@ -29,6 +29,27 @@ def _to_float(value: Any, default: float) -> float: return default +def _to_str(value: Any, default: str = "") -> str: + return str(value).strip() if value is not None else default + + +def _join_posix(base_path: str, suffix_path: str) -> str: + return f"{base_path.rstrip('/')}/{suffix_path.lstrip('/')}" + + +def _resolve_profile_model_path(profile: dict[str, Any], model_root: str, model_key: str) -> str: + local_path = _to_str(profile.get("local_path")) + if not local_path: + raise ValueError(f"model profile '{model_key}' must provide local_path") + if "://" in local_path: + raise ValueError(f"model profile '{model_key}' local_path must be local filesystem path") + if local_path.startswith("/"): + return local_path + if not model_root: + raise ValueError("config.json model_root cannot be empty when local_path is relative") + return _join_posix(model_root, local_path) + + def load_catalog(catalog_path: str = "config.json") -> dict[str, Any]: content = json.loads(Path(catalog_path).read_text(encoding="utf-8")) if not isinstance(content, dict): @@ -50,6 +71,8 @@ def resolve_runtime_settings(content: dict[str, Any]) -> dict[str, Any]: "tensor_parallel_size": _to_int(content.get("tensor_parallel_size"), 2), "dtype": str(content.get("dtype", "bfloat16")), "revision": str(content.get("revision", "")).strip() or None, + "model_root": _to_str(content.get("model_root"), "/opt/model"), + "offline_mode": True, "model_key": str(models.get("selected", "")).strip() or None, } @@ -66,6 +89,7 @@ def resolve_model_profile( profile = profiles[model_key] if not isinstance(profile, dict): raise ValueError(f"model profile '{model_key}' must be a JSON object") + model_root = _to_str(content.get("model_root"), "/opt/model") valid_tp_raw = profile.get("valid_tp", []) valid_tp = [_to_int(item, 0) for item in valid_tp_raw if _to_int(item, 0) > 0] resolved_tp = requested_tp @@ -73,7 +97,7 @@ def resolve_model_profile( resolved_tp = valid_tp[0] updates = { "selected_model": model_key, - "model_name": profile.get("hf_model_id", model_key), + "model_name": _resolve_profile_model_path(profile, model_root, model_key), "served_model_name": profile.get("served_model_name", model_key), "max_model_len": _to_int(profile.get("ctx"), 8192), "max_num_seqs": _to_int(profile.get("max_num_seqs"), 64), @@ -86,4 +110,6 @@ def resolve_model_profile( "enable_auto_tool_choice": _to_bool(profile.get("enable_auto_tool_choice"), False), } env_vars = {str(k): str(v) for k, v in dict(profile.get("env", {})).items()} + env_vars["HF_HUB_OFFLINE"] = "1" + env_vars["TRANSFORMERS_OFFLINE"] = "1" return model_key, updates, env_vars diff --git a/config.json b/config.json index 5984c67..1d389fe 100644 --- a/config.json +++ b/config.json @@ -12,12 +12,15 @@ "api_key": "sk-szcjw", "tensor_parallel_size": 2, "dtype": "bfloat16", + "model_root": "/opt/model", + "offline_mode": true, "revision": "", "models": { "default": "Qwen3.5-35B-A3B-GPTQ-Int4", "selected": "Qwen3.5-35B-A3B-GPTQ-Int4", "profiles": { "Qwen3-Next-80B-A3B-Instruct-AWQ-4bit": { + "local_path": "Qwen3-Next-80B-A3B-Instruct-AWQ-4bit", "ctx": "24576", "trust_remote": true, "valid_tp": [2], @@ -34,6 +37,7 @@ "hf_model_id": "cpatonn/Qwen3-Next-80B-A3B-Instruct-AWQ-4bit" }, "GLM-4.7-Flash-AWQ": { + "local_path": "GLM-4.7-Flash-AWQ", "ctx": "32768", "trust_remote": true, "valid_tp": [1, 2], @@ -46,6 +50,7 @@ "hf_model_id": "THUDM/GLM-4.7-Flash-AWQ" }, "Qwen3.5-27B-FP8": { + "local_path": "Qwen3.5-27B-FP8", "ctx": "32768", "trust_remote": true, "valid_tp": [1, 2], @@ -58,6 +63,7 @@ "hf_model_id": "RedHatAI/Qwen3.5-27B-FP8-dynamic" }, "Qwen3.5-35B-A3B-GPTQ-Int4": { + "local_path": "Qwen3.5-35B-A3B-GPTQ-Int4", "ctx": "32768", "trust_remote": true, "valid_tp": [1, 2], diff --git a/docker-compose.yml b/docker-compose.yml index dc0674f..96975ed 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -7,7 +7,7 @@ services: container_name: rocm-vllm-inference command: ["python", "-m", "app.start_api"] ports: - - "8000:8000" + - "8002:8002" volumes: - /opt/model:/opt/model:ro - ./config.json:/workspace/config.json:ro