hugpy-engine 0.2.2__tar.gz → 0.2.4__tar.gz
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.
- {hugpy_engine-0.2.2/src/hugpy_engine.egg-info → hugpy_engine-0.2.4}/PKG-INFO +1 -1
- {hugpy_engine-0.2.2 → hugpy_engine-0.2.4}/src/hugpy_engine/apis/get_module.py +50 -2
- {hugpy_engine-0.2.2 → hugpy_engine-0.2.4}/src/hugpy_engine/config/main.py +8 -0
- {hugpy_engine-0.2.2 → hugpy_engine-0.2.4}/src/hugpy_engine/config/models/model_meta.py +10 -2
- {hugpy_engine-0.2.2 → hugpy_engine-0.2.4}/src/hugpy_engine/config/models/models_config.py +176 -0
- {hugpy_engine-0.2.2 → hugpy_engine-0.2.4}/src/hugpy_engine/dispatch/activity.py +7 -2
- {hugpy_engine-0.2.2 → hugpy_engine-0.2.4}/src/hugpy_engine/resolvers/__init__.py +2 -1
- {hugpy_engine-0.2.2 → hugpy_engine-0.2.4}/src/hugpy_engine/resolvers/assure_model_key.py +148 -2
- {hugpy_engine-0.2.2 → hugpy_engine-0.2.4}/src/hugpy_engine/resolvers/categories/builders.py +5 -1
- {hugpy_engine-0.2.2 → hugpy_engine-0.2.4}/src/hugpy_engine/resolvers/model_resolver.py +17 -2
- {hugpy_engine-0.2.2 → hugpy_engine-0.2.4}/src/hugpy_engine/resolvers/remote.py +271 -15
- {hugpy_engine-0.2.2 → hugpy_engine-0.2.4}/src/hugpy_engine/schemas/chat_schemas.py +11 -0
- {hugpy_engine-0.2.2 → hugpy_engine-0.2.4}/src/hugpy_engine/schemas/metadata_schemas.py +1 -0
- {hugpy_engine-0.2.2 → hugpy_engine-0.2.4}/src/hugpy_engine/wire/chat_schemas.py +7 -0
- {hugpy_engine-0.2.2 → hugpy_engine-0.2.4/src/hugpy_engine.egg-info}/PKG-INFO +1 -1
- {hugpy_engine-0.2.2 → hugpy_engine-0.2.4}/src/hugpy_engine.egg-info/SOURCES.txt +6 -1
- {hugpy_engine-0.2.2 → hugpy_engine-0.2.4}/src/hugpy_engine.egg-info/scm_file_list.json +5 -0
- hugpy_engine-0.2.4/src/hugpy_engine.egg-info/scm_version.json +8 -0
- hugpy_engine-0.2.4/tests/known_good/test_call_queue_contract.py +357 -0
- hugpy_engine-0.2.4/tests/known_good/test_evict_fit_contract.py +198 -0
- hugpy_engine-0.2.4/tests/known_good/test_model_format_contract.py +193 -0
- hugpy_engine-0.2.4/tests/known_good/test_model_key_contract.py +204 -0
- hugpy_engine-0.2.4/tests/known_good/test_relay_gate_allocation_contract.py +106 -0
- {hugpy_engine-0.2.2 → hugpy_engine-0.2.4}/tests/test_central_cap_gating.py +17 -0
- {hugpy_engine-0.2.2 → hugpy_engine-0.2.4}/tests/test_chat_role_alternation.py +36 -8
- {hugpy_engine-0.2.2 → hugpy_engine-0.2.4}/tests/test_ctx_fit.py +8 -0
- {hugpy_engine-0.2.2 → hugpy_engine-0.2.4}/tests/test_hot_cache.py +27 -0
- {hugpy_engine-0.2.2 → hugpy_engine-0.2.4}/tests/test_per_request_alloc.py +5 -0
- hugpy_engine-0.2.2/src/hugpy_engine.egg-info/scm_version.json +0 -8
- {hugpy_engine-0.2.2 → hugpy_engine-0.2.4}/LICENSE +0 -0
- {hugpy_engine-0.2.2 → hugpy_engine-0.2.4}/README.md +0 -0
- {hugpy_engine-0.2.2 → hugpy_engine-0.2.4}/docs/ALLOCATION-MODES.md +0 -0
- {hugpy_engine-0.2.2 → hugpy_engine-0.2.4}/pyproject.toml +0 -0
- {hugpy_engine-0.2.2 → hugpy_engine-0.2.4}/setup.cfg +0 -0
- {hugpy_engine-0.2.2 → hugpy_engine-0.2.4}/src/hugpy_engine/__init__.py +0 -0
- {hugpy_engine-0.2.2 → hugpy_engine-0.2.4}/src/hugpy_engine/alloc_modes.py +0 -0
- {hugpy_engine-0.2.2 → hugpy_engine-0.2.4}/src/hugpy_engine/allocation.py +0 -0
- {hugpy_engine-0.2.2 → hugpy_engine-0.2.4}/src/hugpy_engine/apis/__init__.py +0 -0
- {hugpy_engine-0.2.2 → hugpy_engine-0.2.4}/src/hugpy_engine/apis/call_api.py +0 -0
- {hugpy_engine-0.2.2 → hugpy_engine-0.2.4}/src/hugpy_engine/apis/reclassify.py +0 -0
- {hugpy_engine-0.2.2 → hugpy_engine-0.2.4}/src/hugpy_engine/apis/reconcile.py +0 -0
- {hugpy_engine-0.2.2 → hugpy_engine-0.2.4}/src/hugpy_engine/apis/serve/__init__.py +0 -0
- {hugpy_engine-0.2.2 → hugpy_engine-0.2.4}/src/hugpy_engine/apis/serve/serve.py +0 -0
- {hugpy_engine-0.2.2 → hugpy_engine-0.2.4}/src/hugpy_engine/apis/serve/serve_cli.py +0 -0
- {hugpy_engine-0.2.2 → hugpy_engine-0.2.4}/src/hugpy_engine/apis/systemd_units.py +0 -0
- {hugpy_engine-0.2.2 → hugpy_engine-0.2.4}/src/hugpy_engine/backend.py +0 -0
- {hugpy_engine-0.2.2 → hugpy_engine-0.2.4}/src/hugpy_engine/backends/__init__.py +0 -0
- {hugpy_engine-0.2.2 → hugpy_engine-0.2.4}/src/hugpy_engine/backends/local.py +0 -0
- {hugpy_engine-0.2.2 → hugpy_engine-0.2.4}/src/hugpy_engine/catalog.py +0 -0
- {hugpy_engine-0.2.2 → hugpy_engine-0.2.4}/src/hugpy_engine/catalog_bridge.py +0 -0
- {hugpy_engine-0.2.2 → hugpy_engine-0.2.4}/src/hugpy_engine/categories.py +0 -0
- {hugpy_engine-0.2.2 → hugpy_engine-0.2.4}/src/hugpy_engine/chat_context/__init__.py +0 -0
- {hugpy_engine-0.2.2 → hugpy_engine-0.2.4}/src/hugpy_engine/chat_context/chat_context.py +0 -0
- {hugpy_engine-0.2.2 → hugpy_engine-0.2.4}/src/hugpy_engine/chat_context/context_budget.py +0 -0
- {hugpy_engine-0.2.2 → hugpy_engine-0.2.4}/src/hugpy_engine/chat_context/unbounded.py +0 -0
- {hugpy_engine-0.2.2 → hugpy_engine-0.2.4}/src/hugpy_engine/config/__init__.py +0 -0
- {hugpy_engine-0.2.2 → hugpy_engine-0.2.4}/src/hugpy_engine/config/models/__init__.py +0 -0
- {hugpy_engine-0.2.2 → hugpy_engine-0.2.4}/src/hugpy_engine/config/models/models_default.py +0 -0
- {hugpy_engine-0.2.2 → hugpy_engine-0.2.4}/src/hugpy_engine/config/models/models_dict.py +0 -0
- {hugpy_engine-0.2.2 → hugpy_engine-0.2.4}/src/hugpy_engine/contracts.py +0 -0
- {hugpy_engine-0.2.2 → hugpy_engine-0.2.4}/src/hugpy_engine/dispatch/__init__.py +0 -0
- {hugpy_engine-0.2.2 → hugpy_engine-0.2.4}/src/hugpy_engine/dispatch/acquire.py +0 -0
- {hugpy_engine-0.2.2 → hugpy_engine-0.2.4}/src/hugpy_engine/dispatch/dispatch.py +0 -0
- {hugpy_engine-0.2.2 → hugpy_engine-0.2.4}/src/hugpy_engine/draft_models.py +0 -0
- {hugpy_engine-0.2.2 → hugpy_engine-0.2.4}/src/hugpy_engine/engines.py +0 -0
- {hugpy_engine-0.2.2 → hugpy_engine-0.2.4}/src/hugpy_engine/eviction.py +0 -0
- {hugpy_engine-0.2.2 → hugpy_engine-0.2.4}/src/hugpy_engine/generate/__init__.py +0 -0
- {hugpy_engine-0.2.2 → hugpy_engine-0.2.4}/src/hugpy_engine/generate/coder.py +0 -0
- {hugpy_engine-0.2.2 → hugpy_engine-0.2.4}/src/hugpy_engine/generate/config.py +0 -0
- {hugpy_engine-0.2.2 → hugpy_engine-0.2.4}/src/hugpy_engine/generate/generate_runner.py +0 -0
- {hugpy_engine-0.2.2 → hugpy_engine-0.2.4}/src/hugpy_engine/gguf_election.py +0 -0
- {hugpy_engine-0.2.2 → hugpy_engine-0.2.4}/src/hugpy_engine/llama/__init__.py +0 -0
- {hugpy_engine-0.2.2 → hugpy_engine-0.2.4}/src/hugpy_engine/llama/runners/__init__.py +0 -0
- {hugpy_engine-0.2.2 → hugpy_engine-0.2.4}/src/hugpy_engine/llama/runners/chat_runner.py +0 -0
- {hugpy_engine-0.2.2 → hugpy_engine-0.2.4}/src/hugpy_engine/llama/runners/get.py +0 -0
- {hugpy_engine-0.2.2 → hugpy_engine-0.2.4}/src/hugpy_engine/llama/runners/src/__init__.py +0 -0
- {hugpy_engine-0.2.2 → hugpy_engine-0.2.4}/src/hugpy_engine/llama/runners/src/base_runner.py +0 -0
- {hugpy_engine-0.2.2 → hugpy_engine-0.2.4}/src/hugpy_engine/llama/runners/src/ccp_runner.py +0 -0
- {hugpy_engine-0.2.2 → hugpy_engine-0.2.4}/src/hugpy_engine/llama/runners/src/imports/__init__.py +0 -0
- {hugpy_engine-0.2.2 → hugpy_engine-0.2.4}/src/hugpy_engine/llama/runners/src/imports/config.py +0 -0
- {hugpy_engine-0.2.2 → hugpy_engine-0.2.4}/src/hugpy_engine/llama/runners/src/imports/constants.py +0 -0
- {hugpy_engine-0.2.2 → hugpy_engine-0.2.4}/src/hugpy_engine/llama/runners/src/imports/init_imports.py +0 -0
- {hugpy_engine-0.2.2 → hugpy_engine-0.2.4}/src/hugpy_engine/llama/runners/src/imports/utils.py +0 -0
- {hugpy_engine-0.2.2 → hugpy_engine-0.2.4}/src/hugpy_engine/llama/runners/src/python_runner.py +0 -0
- {hugpy_engine-0.2.2 → hugpy_engine-0.2.4}/src/hugpy_engine/llama/runners/src/shard_server.py +0 -0
- {hugpy_engine-0.2.2 → hugpy_engine-0.2.4}/src/hugpy_engine/llama/serve.py +0 -0
- {hugpy_engine-0.2.2 → hugpy_engine-0.2.4}/src/hugpy_engine/manifest.py +0 -0
- {hugpy_engine-0.2.2 → hugpy_engine-0.2.4}/src/hugpy_engine/model_classifier.py +0 -0
- {hugpy_engine-0.2.2 → hugpy_engine-0.2.4}/src/hugpy_engine/model_index/__init__.py +0 -0
- {hugpy_engine-0.2.2 → hugpy_engine-0.2.4}/src/hugpy_engine/model_index/client.py +0 -0
- {hugpy_engine-0.2.2 → hugpy_engine-0.2.4}/src/hugpy_engine/model_index/query_registry.py +0 -0
- {hugpy_engine-0.2.2 → hugpy_engine-0.2.4}/src/hugpy_engine/model_index/repositories.py +0 -0
- {hugpy_engine-0.2.2 → hugpy_engine-0.2.4}/src/hugpy_engine/model_index/service.py +0 -0
- {hugpy_engine-0.2.2 → hugpy_engine-0.2.4}/src/hugpy_engine/name_match.py +0 -0
- {hugpy_engine-0.2.2 → hugpy_engine-0.2.4}/src/hugpy_engine/native/__init__.py +0 -0
- {hugpy_engine-0.2.2 → hugpy_engine-0.2.4}/src/hugpy_engine/native/build.py +0 -0
- {hugpy_engine-0.2.2 → hugpy_engine-0.2.4}/src/hugpy_engine/native/fetch.py +0 -0
- {hugpy_engine-0.2.2 → hugpy_engine-0.2.4}/src/hugpy_engine/native/resolve.py +0 -0
- {hugpy_engine-0.2.2 → hugpy_engine-0.2.4}/src/hugpy_engine/output_repair.py +0 -0
- {hugpy_engine-0.2.2 → hugpy_engine-0.2.4}/src/hugpy_engine/peft_adapters.py +0 -0
- {hugpy_engine-0.2.2 → hugpy_engine-0.2.4}/src/hugpy_engine/placement.py +0 -0
- {hugpy_engine-0.2.2 → hugpy_engine-0.2.4}/src/hugpy_engine/py.typed +0 -0
- {hugpy_engine-0.2.2 → hugpy_engine-0.2.4}/src/hugpy_engine/query.py +0 -0
- {hugpy_engine-0.2.2 → hugpy_engine-0.2.4}/src/hugpy_engine/resolvers/allocator.py +0 -0
- {hugpy_engine-0.2.2 → hugpy_engine-0.2.4}/src/hugpy_engine/resolvers/categories/__init__.py +0 -0
- {hugpy_engine-0.2.2 → hugpy_engine-0.2.4}/src/hugpy_engine/resolvers/categories/frameworks.py +0 -0
- {hugpy_engine-0.2.2 → hugpy_engine-0.2.4}/src/hugpy_engine/resolvers/device_placement.py +0 -0
- {hugpy_engine-0.2.2 → hugpy_engine-0.2.4}/src/hugpy_engine/resolvers/groups.py +0 -0
- {hugpy_engine-0.2.2 → hugpy_engine-0.2.4}/src/hugpy_engine/resolvers/model_dict_resolver.py +0 -0
- {hugpy_engine-0.2.2 → hugpy_engine-0.2.4}/src/hugpy_engine/routing_diagnostics.py +0 -0
- {hugpy_engine-0.2.2 → hugpy_engine-0.2.4}/src/hugpy_engine/runtime.py +0 -0
- {hugpy_engine-0.2.2 → hugpy_engine-0.2.4}/src/hugpy_engine/schemas/__init__.py +0 -0
- {hugpy_engine-0.2.2 → hugpy_engine-0.2.4}/src/hugpy_engine/schemas/event_schemas.py +0 -0
- {hugpy_engine-0.2.2 → hugpy_engine-0.2.4}/src/hugpy_engine/schemas/model_schemas.py +0 -0
- {hugpy_engine-0.2.2 → hugpy_engine-0.2.4}/src/hugpy_engine/schemas/runner_schemas.py +0 -0
- {hugpy_engine-0.2.2 → hugpy_engine-0.2.4}/src/hugpy_engine/schemas/task_schemas.py +0 -0
- {hugpy_engine-0.2.2 → hugpy_engine-0.2.4}/src/hugpy_engine/serve/__init__.py +0 -0
- {hugpy_engine-0.2.2 → hugpy_engine-0.2.4}/src/hugpy_engine/serve/chunksum_verify.py +0 -0
- {hugpy_engine-0.2.2 → hugpy_engine-0.2.4}/src/hugpy_engine/serve/hot_cache.py +0 -0
- {hugpy_engine-0.2.2 → hugpy_engine-0.2.4}/src/hugpy_engine/serve/load_failure.py +0 -0
- {hugpy_engine-0.2.2 → hugpy_engine-0.2.4}/src/hugpy_engine/serve/model_cache.py +0 -0
- {hugpy_engine-0.2.2 → hugpy_engine-0.2.4}/src/hugpy_engine/serve/overrides.py +0 -0
- {hugpy_engine-0.2.2 → hugpy_engine-0.2.4}/src/hugpy_engine/serve/policy.py +0 -0
- {hugpy_engine-0.2.2 → hugpy_engine-0.2.4}/src/hugpy_engine/serve/profiles.py +0 -0
- {hugpy_engine-0.2.2 → hugpy_engine-0.2.4}/src/hugpy_engine/serve/serve.py +0 -0
- {hugpy_engine-0.2.2 → hugpy_engine-0.2.4}/src/hugpy_engine/serve/serve_cli.py +0 -0
- {hugpy_engine-0.2.2 → hugpy_engine-0.2.4}/src/hugpy_engine/serve/slot_agent.py +0 -0
- {hugpy_engine-0.2.2 → hugpy_engine-0.2.4}/src/hugpy_engine/serve/slots.py +0 -0
- {hugpy_engine-0.2.2 → hugpy_engine-0.2.4}/src/hugpy_engine/serve/supervisor.py +0 -0
- {hugpy_engine-0.2.2 → hugpy_engine-0.2.4}/src/hugpy_engine/spill.py +0 -0
- {hugpy_engine-0.2.2 → hugpy_engine-0.2.4}/src/hugpy_engine/task_deps.py +0 -0
- {hugpy_engine-0.2.2 → hugpy_engine-0.2.4}/src/hugpy_engine/tasks.py +0 -0
- {hugpy_engine-0.2.2 → hugpy_engine-0.2.4}/src/hugpy_engine/utils/__init__.py +0 -0
- {hugpy_engine-0.2.2 → hugpy_engine-0.2.4}/src/hugpy_engine/utils/json_scavenge.py +0 -0
- {hugpy_engine-0.2.2 → hugpy_engine-0.2.4}/src/hugpy_engine/utils/no_think.py +0 -0
- {hugpy_engine-0.2.2 → hugpy_engine-0.2.4}/src/hugpy_engine/wire/__init__.py +0 -0
- {hugpy_engine-0.2.2 → hugpy_engine-0.2.4}/src/hugpy_engine/wire/config_schemas.py +0 -0
- {hugpy_engine-0.2.2 → hugpy_engine-0.2.4}/src/hugpy_engine/wire/install_schemas.py +0 -0
- {hugpy_engine-0.2.2 → hugpy_engine-0.2.4}/src/hugpy_engine/wire/model_schemas.py +0 -0
- {hugpy_engine-0.2.2 → hugpy_engine-0.2.4}/src/hugpy_engine/wire/request_schemas.py +0 -0
- {hugpy_engine-0.2.2 → hugpy_engine-0.2.4}/src/hugpy_engine/wire/specs_schemas.py +0 -0
- {hugpy_engine-0.2.2 → hugpy_engine-0.2.4}/src/hugpy_engine.egg-info/dependency_links.txt +0 -0
- {hugpy_engine-0.2.2 → hugpy_engine-0.2.4}/src/hugpy_engine.egg-info/entry_points.txt +0 -0
- {hugpy_engine-0.2.2 → hugpy_engine-0.2.4}/src/hugpy_engine.egg-info/requires.txt +0 -0
- {hugpy_engine-0.2.2 → hugpy_engine-0.2.4}/src/hugpy_engine.egg-info/top_level.txt +0 -0
- {hugpy_engine-0.2.2 → hugpy_engine-0.2.4}/tests/conftest.py +0 -0
- {hugpy_engine-0.2.2 → hugpy_engine-0.2.4}/tests/test_admission_gate_and_diagnostics.py +0 -0
- {hugpy_engine-0.2.2 → hugpy_engine-0.2.4}/tests/test_allocation_accuracy.py +0 -0
- {hugpy_engine-0.2.2 → hugpy_engine-0.2.4}/tests/test_budget_bar_spec.py +0 -0
- {hugpy_engine-0.2.2 → hugpy_engine-0.2.4}/tests/test_catalog_bridge.py +0 -0
- {hugpy_engine-0.2.2 → hugpy_engine-0.2.4}/tests/test_chat_request_caller.py +0 -0
- {hugpy_engine-0.2.2 → hugpy_engine-0.2.4}/tests/test_chat_tool_messages_schema.py +0 -0
- {hugpy_engine-0.2.2 → hugpy_engine-0.2.4}/tests/test_chunksum_verify.py +0 -0
- {hugpy_engine-0.2.2 → hugpy_engine-0.2.4}/tests/test_comms_foundations.py +0 -0
- {hugpy_engine-0.2.2 → hugpy_engine-0.2.4}/tests/test_device_placement.py +0 -0
- {hugpy_engine-0.2.2 → hugpy_engine-0.2.4}/tests/test_engine_boundary.py +0 -0
- {hugpy_engine-0.2.2 → hugpy_engine-0.2.4}/tests/test_engine_persisted_resolve.py +0 -0
- {hugpy_engine-0.2.2 → hugpy_engine-0.2.4}/tests/test_eviction_flow.py +0 -0
- {hugpy_engine-0.2.2 → hugpy_engine-0.2.4}/tests/test_feasible_distribution_engine.py +0 -0
- {hugpy_engine-0.2.2 → hugpy_engine-0.2.4}/tests/test_gguf_election.py +0 -0
- {hugpy_engine-0.2.2 → hugpy_engine-0.2.4}/tests/test_gguf_sharded_effective.py +0 -0
- {hugpy_engine-0.2.2 → hugpy_engine-0.2.4}/tests/test_image_task_classification.py +0 -0
- {hugpy_engine-0.2.2 → hugpy_engine-0.2.4}/tests/test_import_policy.py +0 -0
- {hugpy_engine-0.2.2 → hugpy_engine-0.2.4}/tests/test_load_failure_reason.py +0 -0
- {hugpy_engine-0.2.2 → hugpy_engine-0.2.4}/tests/test_mmproj_effective_bytes.py +0 -0
- {hugpy_engine-0.2.2 → hugpy_engine-0.2.4}/tests/test_model_aliases.py +0 -0
- {hugpy_engine-0.2.2 → hugpy_engine-0.2.4}/tests/test_no_makeroom.py +0 -0
- {hugpy_engine-0.2.2 → hugpy_engine-0.2.4}/tests/test_no_think.py +0 -0
- {hugpy_engine-0.2.2 → hugpy_engine-0.2.4}/tests/test_peft_adapter_load.py +0 -0
- {hugpy_engine-0.2.2 → hugpy_engine-0.2.4}/tests/test_per_device_slot.py +0 -0
- {hugpy_engine-0.2.2 → hugpy_engine-0.2.4}/tests/test_placement_defaults.py +0 -0
- {hugpy_engine-0.2.2 → hugpy_engine-0.2.4}/tests/test_provision_failure_honesty.py +0 -0
- {hugpy_engine-0.2.2 → hugpy_engine-0.2.4}/tests/test_registry_attribution.py +0 -0
- {hugpy_engine-0.2.2 → hugpy_engine-0.2.4}/tests/test_registry_symmetry.py +0 -0
- {hugpy_engine-0.2.2 → hugpy_engine-0.2.4}/tests/test_resolve_routing_key_reconcile.py +0 -0
- {hugpy_engine-0.2.2 → hugpy_engine-0.2.4}/tests/test_runner_registry_heartbeat_starvation.py +0 -0
- {hugpy_engine-0.2.2 → hugpy_engine-0.2.4}/tests/test_slot_load_stall_health.py +0 -0
- {hugpy_engine-0.2.2 → hugpy_engine-0.2.4}/tests/test_slot_moe_vram_preflight.py +0 -0
- {hugpy_engine-0.2.2 → hugpy_engine-0.2.4}/tests/test_slot_placement_projection.py +0 -0
- {hugpy_engine-0.2.2 → hugpy_engine-0.2.4}/tests/test_slot_port_guard.py +0 -0
- {hugpy_engine-0.2.2 → hugpy_engine-0.2.4}/tests/test_slot_relaunch.py +0 -0
- {hugpy_engine-0.2.2 → hugpy_engine-0.2.4}/tests/test_spill_ctx_reserve.py +0 -0
- {hugpy_engine-0.2.2 → hugpy_engine-0.2.4}/tests/test_spill_vision_fit.py +0 -0
- {hugpy_engine-0.2.2 → hugpy_engine-0.2.4}/tests/test_tasks_registry.py +0 -0
- {hugpy_engine-0.2.2 → hugpy_engine-0.2.4}/tests/test_tok_s_plausible_ceiling.py +0 -0
- {hugpy_engine-0.2.2 → hugpy_engine-0.2.4}/tests/test_transformers_placement.py +0 -0
- {hugpy_engine-0.2.2 → hugpy_engine-0.2.4}/tests/test_video_model_classification.py +0 -0
- {hugpy_engine-0.2.2 → hugpy_engine-0.2.4}/tests/test_vision_needs_slot.py +0 -0
- {hugpy_engine-0.2.2 → hugpy_engine-0.2.4}/tests/test_vram_cliff.py +0 -0
- {hugpy_engine-0.2.2 → hugpy_engine-0.2.4}/tests/test_worker_error_attribution.py +0 -0
- {hugpy_engine-0.2.2 → hugpy_engine-0.2.4}/tests/test_worker_serve_path_central_only.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: hugpy-engine
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.4
|
|
4
4
|
Summary: Hugpy engine: model discovery and classification, allocation and eviction, native llama.cpp and GGUF runners, and the prompt-to-reply inference path with task and placement seams
|
|
5
5
|
Author-email: putkoff <support@hugpy.ai>
|
|
6
6
|
License-Expression: LicenseRef-Proprietary
|
|
@@ -133,6 +133,25 @@ def get_all_configs(verbose: bool = False, get_code: bool = False,
|
|
|
133
133
|
|
|
134
134
|
hub_id = clean_hub_id(directory, folder)
|
|
135
135
|
framework = (marker or {}).get("framework") or infer_framework(directory)
|
|
136
|
+
marked_context = (marker or {}).get("model_max_length")
|
|
137
|
+
if (isinstance(marked_context, (int, float)) and
|
|
138
|
+
not isinstance(marked_context, bool) and marked_context > 0):
|
|
139
|
+
max_model_length = int(marked_context)
|
|
140
|
+
elif framework == "gguf":
|
|
141
|
+
# Legacy registry walk also needs the trained context. The generic
|
|
142
|
+
# JSON scanner has no GGUF metadata and otherwise substitutes the
|
|
143
|
+
# global default for models whose marker predates context capture.
|
|
144
|
+
try:
|
|
145
|
+
from hugpy_engine.config.main import get_gguf_file
|
|
146
|
+
from hugpy_storage.gguf_inspect import gguf_metadata
|
|
147
|
+
gguf_path = get_gguf_file(
|
|
148
|
+
directory, None, prefer=(marker or {}).get("filename"))
|
|
149
|
+
context = (gguf_metadata(gguf_path, (".context_length",))
|
|
150
|
+
.get(".context_length") if gguf_path else None)
|
|
151
|
+
if isinstance(context, (int, float)) and not isinstance(context, bool) and context > 0:
|
|
152
|
+
max_model_length = int(context)
|
|
153
|
+
except Exception as exc: # noqa: BLE001 — best-effort metadata only
|
|
154
|
+
logger.debug("GGUF context unavailable for %s: %s", name, exc)
|
|
136
155
|
tasks = (marker or {}).get("tasks") or tasks_data.get(name)
|
|
137
156
|
primary_task = (marker or {}).get("primary_task") or (tasks[0] if tasks else None)
|
|
138
157
|
|
|
@@ -278,12 +297,36 @@ def resolve_hugpy_marker(directory: str, hub_id: str) -> dict:
|
|
|
278
297
|
tasks = marker.get("tasks")
|
|
279
298
|
if tasks is not None and not isinstance(tasks, list):
|
|
280
299
|
tasks = [tasks]
|
|
281
|
-
|
|
300
|
+
out = {k: v for k, v in {
|
|
282
301
|
"name": marker.get("name"),
|
|
283
302
|
"framework": marker.get("framework"),
|
|
284
303
|
"tasks": tasks,
|
|
285
304
|
"primary_task": marker.get("primary_task"),
|
|
286
305
|
}.items() if v is not None}
|
|
306
|
+
context = marker.get("model_max_length")
|
|
307
|
+
if isinstance(context, (int, float)) and not isinstance(context, bool) and context > 0:
|
|
308
|
+
out["model_max_length"] = int(context)
|
|
309
|
+
return out
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
def resolve_gguf_context(directory: str, hub_id: str) -> dict:
|
|
313
|
+
"""Read the selected GGUF's trained context from its header."""
|
|
314
|
+
try:
|
|
315
|
+
from hugpy_storage.hugpy_marker import read_hugpy_marker
|
|
316
|
+
marker = read_hugpy_marker(directory) or {}
|
|
317
|
+
if (marker.get("framework") or infer_framework(directory)) != "gguf":
|
|
318
|
+
return {}
|
|
319
|
+
from hugpy_engine.config.main import get_gguf_file
|
|
320
|
+
from hugpy_storage.gguf_inspect import gguf_metadata
|
|
321
|
+
gguf = get_gguf_file(directory, None, prefer=marker.get("filename"))
|
|
322
|
+
if not gguf:
|
|
323
|
+
return {}
|
|
324
|
+
value = gguf_metadata(gguf, (".context_length",)).get(".context_length")
|
|
325
|
+
if isinstance(value, (int, float)) and not isinstance(value, bool) and value > 0:
|
|
326
|
+
return {"model_max_length": int(value)}
|
|
327
|
+
except Exception as exc: # noqa: BLE001 — metadata inspection must not block discovery
|
|
328
|
+
logger.debug("GGUF context unavailable for %s: %s", hub_id, exc)
|
|
329
|
+
return {}
|
|
287
330
|
|
|
288
331
|
|
|
289
332
|
# Storage families that imply a framework. "misc" implies nothing — comfy and
|
|
@@ -374,6 +417,7 @@ def build_resolver_chain(*, api: Any = None,
|
|
|
374
417
|
chain: List[Tuple[str, ResolverFn]] = [
|
|
375
418
|
("dir_declaration", resolve_dir_declaration),
|
|
376
419
|
("hugpy_marker", resolve_hugpy_marker),
|
|
420
|
+
("gguf_context", resolve_gguf_context),
|
|
377
421
|
("local_config", resolve_local_config),
|
|
378
422
|
("local_tokenizer", resolve_local_tokenizer),
|
|
379
423
|
]
|
|
@@ -441,7 +485,8 @@ def discover_model(save_json: bool = True, verbose: bool = True, use_hub: bool =
|
|
|
441
485
|
"architectures": meta.architectures,
|
|
442
486
|
"model_type": meta.model_type,
|
|
443
487
|
"max_position_embeddings": meta.max_position_embeddings,
|
|
444
|
-
"model_max_length": meta.
|
|
488
|
+
"model_max_length": meta.model_max_length
|
|
489
|
+
or meta.tokenizer_model_max_length
|
|
445
490
|
or meta.max_position_embeddings
|
|
446
491
|
or DEFAULT_MAX_TOKENS,
|
|
447
492
|
"parameter_count": meta.parameter_count,
|
|
@@ -484,6 +529,9 @@ def discover_models(save_json: bool = True, verbose: bool = True, use_hub: bool
|
|
|
484
529
|
meta, meta_sources = enrich(directory, hub_id, chain)
|
|
485
530
|
|
|
486
531
|
row = meta.to_dict()
|
|
532
|
+
row["model_max_length"] = (meta.model_max_length
|
|
533
|
+
or meta.tokenizer_model_max_length
|
|
534
|
+
or meta.max_position_embeddings)
|
|
487
535
|
# Declared name (hugpy.json) wins; the bare basename is the fallback —
|
|
488
536
|
# a marker-stamped "comfy-sd-turbo" must not degrade to "sd-turbo" and
|
|
489
537
|
# collide with the staple's display name.
|
|
@@ -101,6 +101,14 @@ def get_model_path(key: str):
|
|
|
101
101
|
return env_override
|
|
102
102
|
cfg = get_model_config(key)
|
|
103
103
|
path = os.path.join(MODELS_HOME,cfg.folder)
|
|
104
|
+
# A managed copy takes precedence. The worker's external pointer is used
|
|
105
|
+
# only when its Hugpy cache does not hold the model.
|
|
106
|
+
external = (getattr(cfg, "extra", None) or {}).get("external_location")
|
|
107
|
+
if external and not model_looks_downloaded(path, cfg) and (
|
|
108
|
+
os.path.isdir(external) or
|
|
109
|
+
((getattr(cfg, "extra", None) or {}).get("ollama_model") and
|
|
110
|
+
os.path.isfile(external))):
|
|
111
|
+
return external
|
|
104
112
|
# Read-through (2026-07-12 hotfix): cfg.folder is CENTRAL's layout — after
|
|
105
113
|
# the store flattening it names the FLAT dir, but a worker's files may
|
|
106
114
|
# still sit under a legacy task path. Without the resolver here, every
|
|
@@ -142,12 +142,18 @@ def model_meta(cfg: Any, *, vram_bytes: Optional[int] = None,
|
|
|
142
142
|
framework = d.get("framework") or ""
|
|
143
143
|
model_dir = _model_dir(d)
|
|
144
144
|
dir_bytes = dir_size_bytes(model_dir) # whole dir (every file/variant)
|
|
145
|
+
# Host discovery may know the exact effective checkpoint size even though
|
|
146
|
+
# Central cannot read that worker's path (Ollama blobs and API-served
|
|
147
|
+
# vLLM checkpoints are common examples). Use that measured catalog value
|
|
148
|
+
# when no local directory is available; local GGUF resolution below still
|
|
149
|
+
# takes precedence when the weights are on Central.
|
|
150
|
+
discovered_size = d.get("effective_bytes") or d.get("size_bytes")
|
|
145
151
|
|
|
146
152
|
# A GGUF repo often holds several quantizations but only ONE serves, so the
|
|
147
153
|
# dir sum badly overstates "the model" — and it flowed straight into the VRAM
|
|
148
154
|
# recommendation. Resolve the single effective quant (+ its mmproj) exactly as
|
|
149
155
|
# the runner will, honoring the operator's gguf_file choice, and size by that.
|
|
150
|
-
size = dir_bytes
|
|
156
|
+
size = dir_bytes or discovered_size
|
|
151
157
|
gguf = {}
|
|
152
158
|
if str(framework).lower() in ("gguf", "llama_cpp") and model_dir:
|
|
153
159
|
try:
|
|
@@ -157,6 +163,8 @@ def model_meta(cfg: Any, *, vram_bytes: Optional[int] = None,
|
|
|
157
163
|
gguf = {}
|
|
158
164
|
if gguf.get("effective_bytes"):
|
|
159
165
|
size = gguf["effective_bytes"]
|
|
166
|
+
elif discovered_size:
|
|
167
|
+
size = discovered_size
|
|
160
168
|
|
|
161
169
|
selected = selected_err = None
|
|
162
170
|
gguf_sel = str(select_gguf or "").strip()
|
|
@@ -177,7 +185,7 @@ def model_meta(cfg: Any, *, vram_bytes: Optional[int] = None,
|
|
|
177
185
|
out = {
|
|
178
186
|
"model_key": d.get("model_key"),
|
|
179
187
|
"size_bytes": size, # effective quant for GGUF
|
|
180
|
-
"dir_bytes": dir_bytes
|
|
188
|
+
"dir_bytes": dir_bytes or (discovered_size if not model_dir else None),
|
|
181
189
|
"quant": parse_quant(gguf.get("effective_gguf") or filename),
|
|
182
190
|
"params_b": parse_params_b(name, hub_id, filename),
|
|
183
191
|
"ctx_max": ctx_max,
|
|
@@ -16,6 +16,8 @@ refresh_registry() explicitly — e.g. on hugpy module startup.
|
|
|
16
16
|
|
|
17
17
|
import json
|
|
18
18
|
import os
|
|
19
|
+
import time
|
|
20
|
+
import hashlib
|
|
19
21
|
from dataclasses import MISSING, fields
|
|
20
22
|
from typing import Dict
|
|
21
23
|
from abstract_essentials import get_logFile, safe_dump_to_file, safe_load_from_json
|
|
@@ -773,6 +775,7 @@ def derive_model_config_row(name, row):
|
|
|
773
775
|
or row.get("tokenizer_model_max_length")
|
|
774
776
|
or row.get("max_position_embeddings") or DEFAULT_MAX_TOKENS_LOCAL,
|
|
775
777
|
"filename": row.get("filename"), "include": row.get("include"),
|
|
778
|
+
**({"ollama_model": row["ollama_model"]} if row.get("ollama_model") else {}),
|
|
776
779
|
"port": row.get("port"), "host": row.get("host"),
|
|
777
780
|
# SERVEABLE = at least one advertised task has a runner (k61). It used to
|
|
778
781
|
# mean "EVERY task has one", which made a partially-servable row read as
|
|
@@ -813,6 +816,10 @@ def _absorb_disk(staple, disc):
|
|
|
813
816
|
for k in ("dir", "folder", "filename"):
|
|
814
817
|
if disc.get(k):
|
|
815
818
|
staple[k] = disc[k]
|
|
819
|
+
for k in ("model_max_length", "tokenizer_model_max_length",
|
|
820
|
+
"max_position_embeddings"):
|
|
821
|
+
if disc.get(k) is not None:
|
|
822
|
+
staple[k] = disc[k]
|
|
816
823
|
|
|
817
824
|
def merge_discovery_into_models(discovery, base=None):
|
|
818
825
|
base = base if base is not None else MODELS
|
|
@@ -1492,6 +1499,8 @@ def get_models_dict(models_dict_path=None, dict_return=False, return_dict=False,
|
|
|
1492
1499
|
# discovery/path (a fresh walk) forces a rebuild. During the import-time
|
|
1493
1500
|
# build the globals aren't bound yet -> globals().get() is None -> build.
|
|
1494
1501
|
if discovery is None and models_dict_path is None:
|
|
1502
|
+
if "_apply_worker_catalog" in globals():
|
|
1503
|
+
_apply_worker_catalog()
|
|
1495
1504
|
cached = globals().get("MODEL_REGISTRY_DICT" if dict_return else "MODEL_REGISTRY")
|
|
1496
1505
|
if cached:
|
|
1497
1506
|
return cached
|
|
@@ -1536,6 +1545,171 @@ def get_models_dict(models_dict_path=None, dict_return=False, return_dict=False,
|
|
|
1536
1545
|
MODEL_REGISTRY: Dict[str, ModelConfig] = get_models_dict()
|
|
1537
1546
|
MODEL_REGISTRY_DICT: Dict[str, dict] = get_models_dict(dict_return=True)
|
|
1538
1547
|
|
|
1548
|
+
# A worker owns the path to its external weights. Central stores only the
|
|
1549
|
+
# catalog metadata and a per-worker path pointer; it must never resolve that
|
|
1550
|
+
# pointer as a path on Central or offer it as a central transfer source.
|
|
1551
|
+
_WORKER_CATALOG_PATH = os.path.join(os.path.dirname(MODELS_DISCOVERY_PATH),
|
|
1552
|
+
"worker_model_catalog.json")
|
|
1553
|
+
_WORKER_CATALOG_SEEN = 0.0
|
|
1554
|
+
_WORKER_ONLY_KEYS: set[str] = set()
|
|
1555
|
+
_WORKER_TOUCHED_KEYS: set[str] = set()
|
|
1556
|
+
|
|
1557
|
+
|
|
1558
|
+
def _central_copy_present(key: str) -> bool:
|
|
1559
|
+
"""A registry row alone does not prove Central owns transferable bytes."""
|
|
1560
|
+
try:
|
|
1561
|
+
from hugpy_engine.config.main import get_model_path
|
|
1562
|
+
from hugpy_storage.model_presence import model_looks_downloaded
|
|
1563
|
+
cfg = MODEL_REGISTRY[key]
|
|
1564
|
+
return model_looks_downloaded(get_model_path(key), cfg)
|
|
1565
|
+
except Exception: # noqa: BLE001 — uncertain means no destructive cold reset
|
|
1566
|
+
return False
|
|
1567
|
+
|
|
1568
|
+
|
|
1569
|
+
def record_worker_models(worker_id: str, rows: dict) -> list[str]:
|
|
1570
|
+
"""Persist one host scan, shared by all Central web processes."""
|
|
1571
|
+
import fcntl
|
|
1572
|
+
path = _WORKER_CATALOG_PATH
|
|
1573
|
+
os.makedirs(os.path.dirname(path), exist_ok=True)
|
|
1574
|
+
with open(path + ".lock", "a+") as lock:
|
|
1575
|
+
fcntl.flock(lock, fcntl.LOCK_EX)
|
|
1576
|
+
try:
|
|
1577
|
+
with open(path, encoding="utf-8") as fh:
|
|
1578
|
+
data = json.load(fh)
|
|
1579
|
+
except (OSError, ValueError):
|
|
1580
|
+
data = {}
|
|
1581
|
+
clean = {}
|
|
1582
|
+
for key, row in (rows or {}).items():
|
|
1583
|
+
if not isinstance(key, str) or not isinstance(row, dict):
|
|
1584
|
+
continue
|
|
1585
|
+
location = row.get("external_location")
|
|
1586
|
+
api_url = row.get("api_url")
|
|
1587
|
+
try:
|
|
1588
|
+
from urllib.parse import urlsplit
|
|
1589
|
+
parsed_api = urlsplit(api_url) if isinstance(api_url, str) else None
|
|
1590
|
+
is_api = bool(parsed_api and parsed_api.scheme == "http"
|
|
1591
|
+
and parsed_api.hostname in ("127.0.0.1", "localhost", "::1")
|
|
1592
|
+
and parsed_api.port and not parsed_api.username
|
|
1593
|
+
and not parsed_api.password and parsed_api.path in ("", "/")
|
|
1594
|
+
and not parsed_api.query and not parsed_api.fragment)
|
|
1595
|
+
except ValueError:
|
|
1596
|
+
is_api = False
|
|
1597
|
+
if not ((isinstance(location, str) and os.path.isabs(location)) or is_api):
|
|
1598
|
+
continue
|
|
1599
|
+
hub = str(row.get("hub_id") or "").strip("/").lower()
|
|
1600
|
+
current = MODEL_REGISTRY.get(key)
|
|
1601
|
+
if (not is_api and current is not None and
|
|
1602
|
+
str(current.hub_id or "").strip("/").lower() != hub):
|
|
1603
|
+
owner = str(row.get("hub_id") or "local").split("/", 1)[0]
|
|
1604
|
+
key = f"{owner}~{key}"
|
|
1605
|
+
current = MODEL_REGISTRY.get(key)
|
|
1606
|
+
if ((not is_api and current is not None and
|
|
1607
|
+
str(current.hub_id or "").strip("/").lower() != hub) or
|
|
1608
|
+
(key in clean and clean[key].get("worker_location") != location
|
|
1609
|
+
and clean[key].get("api_url") != api_url)):
|
|
1610
|
+
key += "~" + hashlib.sha256(location.encode()).hexdigest()[:8]
|
|
1611
|
+
clean[key] = {k: row.get(k) for k in (
|
|
1612
|
+
"name", "hub_id", "framework", "tasks", "primary_task",
|
|
1613
|
+
"filename", "model_max_length", "model_key", "size_bytes",
|
|
1614
|
+
"effective_bytes", "ollama_model", "api_url", "served_model",
|
|
1615
|
+
"service_kind") if row.get(k) is not None}
|
|
1616
|
+
clean[key]["model_key"] = key
|
|
1617
|
+
if isinstance(location, str) and os.path.isabs(location):
|
|
1618
|
+
clean[key]["worker_location"] = location
|
|
1619
|
+
old = (data.get(worker_id) or {}).get("models")
|
|
1620
|
+
changed = old != clean
|
|
1621
|
+
if changed:
|
|
1622
|
+
data[worker_id] = {"at": time.time(), "models": clean}
|
|
1623
|
+
tmp = path + f".{os.getpid()}.tmp"
|
|
1624
|
+
with open(tmp, "w", encoding="utf-8") as fh:
|
|
1625
|
+
json.dump(data, fh)
|
|
1626
|
+
os.replace(tmp, path)
|
|
1627
|
+
fcntl.flock(lock, fcntl.LOCK_UN)
|
|
1628
|
+
_apply_worker_catalog(force=changed)
|
|
1629
|
+
# The canonical keys may differ from worker-local scan keys when central
|
|
1630
|
+
# already has a same-named model from another hub. Return those identities
|
|
1631
|
+
# so heartbeat placement uses the same keys as the shared model catalog.
|
|
1632
|
+
return sorted(clean)
|
|
1633
|
+
|
|
1634
|
+
|
|
1635
|
+
def _apply_worker_catalog(force=False) -> None:
|
|
1636
|
+
"""Overlay worker-only rows on the regular catalog, preserving local rows."""
|
|
1637
|
+
global _WORKER_CATALOG_SEEN
|
|
1638
|
+
registry = globals().get("MODEL_REGISTRY")
|
|
1639
|
+
registry_dict = globals().get("MODEL_REGISTRY_DICT")
|
|
1640
|
+
if registry is None or registry_dict is None:
|
|
1641
|
+
return # import-time base build
|
|
1642
|
+
try:
|
|
1643
|
+
stamp = os.stat(_WORKER_CATALOG_PATH).st_mtime_ns
|
|
1644
|
+
if not force and stamp == _WORKER_CATALOG_SEEN:
|
|
1645
|
+
return
|
|
1646
|
+
with open(_WORKER_CATALOG_PATH, encoding="utf-8") as fh:
|
|
1647
|
+
data = json.load(fh)
|
|
1648
|
+
except (OSError, ValueError):
|
|
1649
|
+
return
|
|
1650
|
+
locations: dict[str, dict[str, str]] = {}
|
|
1651
|
+
external_apis: dict[str, dict[str, dict]] = {}
|
|
1652
|
+
candidates: dict[str, dict] = {}
|
|
1653
|
+
for worker_id, entry in data.items():
|
|
1654
|
+
for key, row in (entry.get("models") or {}).items():
|
|
1655
|
+
if not isinstance(row, dict):
|
|
1656
|
+
continue
|
|
1657
|
+
if row.get("worker_location"):
|
|
1658
|
+
locations.setdefault(key, {})[worker_id] = row["worker_location"]
|
|
1659
|
+
if row.get("api_url"):
|
|
1660
|
+
external_apis.setdefault(key, {})[worker_id] = {
|
|
1661
|
+
field: row[field] for field in ("api_url", "served_model", "service_kind")
|
|
1662
|
+
if row.get(field) is not None}
|
|
1663
|
+
candidates.setdefault(key, row)
|
|
1664
|
+
|
|
1665
|
+
for key in list(_WORKER_ONLY_KEYS - candidates.keys()):
|
|
1666
|
+
registry.pop(key, None)
|
|
1667
|
+
registry_dict.pop(key, None)
|
|
1668
|
+
_WORKER_ONLY_KEYS.discard(key)
|
|
1669
|
+
for key in _WORKER_TOUCHED_KEYS - candidates.keys():
|
|
1670
|
+
if key in registry_dict:
|
|
1671
|
+
registry_dict[key].pop("worker_locations", None)
|
|
1672
|
+
registry_dict[key].pop("worker_external_apis", None)
|
|
1673
|
+
registry_dict[key].pop("worker_only", None)
|
|
1674
|
+
if key in registry:
|
|
1675
|
+
registry[key].extra.pop("worker_only", None)
|
|
1676
|
+
for key, row in candidates.items():
|
|
1677
|
+
if key not in registry:
|
|
1678
|
+
clean = {k: v for k, v in row.items() if k != "worker_location"}
|
|
1679
|
+
derived, _ = derive_model_config_row(key, clean)
|
|
1680
|
+
if not derived:
|
|
1681
|
+
continue
|
|
1682
|
+
derived["worker_only"] = True
|
|
1683
|
+
for size_key in ("size_bytes", "effective_bytes"):
|
|
1684
|
+
if row.get(size_key) is not None:
|
|
1685
|
+
derived[size_key] = row[size_key]
|
|
1686
|
+
cfg = get_assessed_model_config(derived)
|
|
1687
|
+
if not cfg:
|
|
1688
|
+
continue
|
|
1689
|
+
registry[key] = cfg
|
|
1690
|
+
registry_dict[key] = cfg.to_dict()
|
|
1691
|
+
for size_key in ("size_bytes", "effective_bytes"):
|
|
1692
|
+
if row.get(size_key) is not None:
|
|
1693
|
+
registry_dict[key][size_key] = row[size_key]
|
|
1694
|
+
_WORKER_ONLY_KEYS.add(key)
|
|
1695
|
+
worker_only = key in _WORKER_ONLY_KEYS or not _central_copy_present(key)
|
|
1696
|
+
registry_dict[key]["worker_locations"] = locations.get(key, {})
|
|
1697
|
+
if external_apis.get(key):
|
|
1698
|
+
registry_dict[key]["worker_external_apis"] = external_apis[key]
|
|
1699
|
+
else:
|
|
1700
|
+
registry_dict[key].pop("worker_external_apis", None)
|
|
1701
|
+
registry_dict[key]["worker_only"] = worker_only
|
|
1702
|
+
if worker_only:
|
|
1703
|
+
registry[key].extra["worker_only"] = True
|
|
1704
|
+
else:
|
|
1705
|
+
registry[key].extra.pop("worker_only", None)
|
|
1706
|
+
_WORKER_TOUCHED_KEYS.clear()
|
|
1707
|
+
_WORKER_TOUCHED_KEYS.update(candidates)
|
|
1708
|
+
_WORKER_CATALOG_SEEN = stamp
|
|
1709
|
+
|
|
1710
|
+
|
|
1711
|
+
_apply_worker_catalog(force=True)
|
|
1712
|
+
|
|
1539
1713
|
|
|
1540
1714
|
def get_model_registry(dict_return=False, return_dict=False):
|
|
1541
1715
|
dict_return = dict_return or return_dict
|
|
@@ -1569,6 +1743,8 @@ def refresh_registry(run_discovery=True):
|
|
|
1569
1743
|
MODEL_REGISTRY_DICT.update(fresh_dict)
|
|
1570
1744
|
for stale in [k for k in MODEL_REGISTRY_DICT if k not in fresh_dict]:
|
|
1571
1745
|
MODEL_REGISTRY_DICT.pop(stale, None)
|
|
1746
|
+
_WORKER_ONLY_KEYS.clear() # fresh is the central base; recalculate ownership
|
|
1747
|
+
_apply_worker_catalog(force=True)
|
|
1572
1748
|
try:
|
|
1573
1749
|
from hugpy_engine.config.models.models_default import refresh_task_registries
|
|
1574
1750
|
refresh_task_registries()
|
|
@@ -32,7 +32,10 @@ def format_prompt(messages=None, prompt=None) -> str:
|
|
|
32
32
|
return json.dumps(messages, ensure_ascii=False, indent=2, default=str)
|
|
33
33
|
|
|
34
34
|
# view state <- canonical status
|
|
35
|
-
|
|
35
|
+
# Keep queue presentation aligned with the canonical job lifecycle. A request
|
|
36
|
+
# assigned to a worker but still pre-filling/loading is processing, not waiting;
|
|
37
|
+
# collapsing the two made a live worker request look orphaned in the console.
|
|
38
|
+
_VIEW_STATE = {"pending": "waiting", "processing": "processing",
|
|
36
39
|
"streaming": "active"}
|
|
37
40
|
_QUEUE_KINDS_EXCLUDED = {"download"}
|
|
38
41
|
|
|
@@ -105,6 +108,8 @@ def counts() -> dict:
|
|
|
105
108
|
if d["kind"] not in _QUEUE_KINDS_EXCLUDED]
|
|
106
109
|
waiting = sum(1 for d in live
|
|
107
110
|
if _VIEW_STATE.get(d["status"]) == "waiting")
|
|
111
|
+
# Preserve the existing API shape (`active` is the non-waiting bucket),
|
|
112
|
+
# while snapshots distinguish `processing` from token-producing `active`.
|
|
108
113
|
active = sum(1 for d in live
|
|
109
|
-
if _VIEW_STATE.get(d["status"])
|
|
114
|
+
if _VIEW_STATE.get(d["status"]) in ("processing", "active"))
|
|
110
115
|
return {"waiting": waiting, "active": active, "total": waiting + active}
|
|
@@ -14,6 +14,7 @@ from hugpy_engine.resolvers.remote import (
|
|
|
14
14
|
set_no_worker_diagnostic,
|
|
15
15
|
set_no_worker_skips,
|
|
16
16
|
set_placement_provider,
|
|
17
|
+
set_serving_provider,
|
|
17
18
|
set_worker_provider,
|
|
18
19
|
)
|
|
19
20
|
|
|
@@ -21,5 +22,5 @@ __all__ = [
|
|
|
21
22
|
"EXTERNAL_TASK_RUNNERS", "Peer", "ensure_staple_weights", "external_runner_for",
|
|
22
23
|
"get_worker_provider", "peer_for", "resolve", "resolve_model_key",
|
|
23
24
|
"set_no_worker_diagnostic", "set_no_worker_skips", "set_placement_provider",
|
|
24
|
-
"set_worker_provider", "validate_registry",
|
|
25
|
+
"set_serving_provider", "set_worker_provider", "validate_registry",
|
|
25
26
|
]
|
|
@@ -320,6 +320,102 @@ def _quant_rank(key: str) -> int:
|
|
|
320
320
|
return max((_QUANT_BITS[t] for t in toks if t in _QUANT_BITS), default=0)
|
|
321
321
|
|
|
322
322
|
|
|
323
|
+
# Packaging / quant / framework markers — a name carrying any of these named a
|
|
324
|
+
# representation, not merely a base-name family. Single source for both the
|
|
325
|
+
# serving-override gate (_package_specific) and the logical-identity strip
|
|
326
|
+
# (_logical_id) so the two never drift.
|
|
327
|
+
_FORMAT_TOKENS = frozenset({
|
|
328
|
+
"gguf", "transformers", "transformer", "safetensors", "pytorch",
|
|
329
|
+
"awq", "gptq", "exl2", "exl3", "mlx", "bnb", "4bit", "8bit",
|
|
330
|
+
"fp8", "fp16", "bf16", "f16", "q2", "q3", "q4", "q5", "q6", "q8",
|
|
331
|
+
})
|
|
332
|
+
|
|
333
|
+
# The GGUF engine family (mirrors alloc_modes.GGUF_ENGINES; inlined to keep this
|
|
334
|
+
# resolver import-cheap and cycle-free).
|
|
335
|
+
_GGUF_ENGINES = frozenset({"gguf", "llama_cpp"})
|
|
336
|
+
|
|
337
|
+
|
|
338
|
+
def _package_specific(model_key: str) -> bool:
|
|
339
|
+
"""Whether the caller explicitly selected a model packaging/framework.
|
|
340
|
+
|
|
341
|
+
A bare family request (``Qwen3-Coder-Next``) must follow the live serving
|
|
342
|
+
representation, whether that is GGUF or Transformers. ``GGUF`` and the
|
|
343
|
+
common Transformers/package markers are deliberate specificity and must
|
|
344
|
+
remain exact rather than being rewritten to whichever sibling is hot.
|
|
345
|
+
"""
|
|
346
|
+
return bool(set(_tokens(model_key)) & _FORMAT_TOKENS)
|
|
347
|
+
|
|
348
|
+
|
|
349
|
+
def _framework_of(key: str):
|
|
350
|
+
"""The registry framework for ``key`` (scalar), or None when absent."""
|
|
351
|
+
cfg = MODEL_REGISTRY.get(key)
|
|
352
|
+
fw = getattr(cfg, "framework", None) if cfg is not None else None
|
|
353
|
+
if isinstance(fw, (list, tuple)):
|
|
354
|
+
fw = fw[0] if fw else None
|
|
355
|
+
return fw
|
|
356
|
+
|
|
357
|
+
|
|
358
|
+
def _representation_of(key: str):
|
|
359
|
+
"""The gguf-vs-transformers REPRESENTATION of a registry key, read from its
|
|
360
|
+
framework (never a name substring). GGUF family -> "gguf"; any other
|
|
361
|
+
concrete framework -> "transformers"; None when the key is not in the
|
|
362
|
+
registry."""
|
|
363
|
+
fw = _framework_of(key)
|
|
364
|
+
if fw is None:
|
|
365
|
+
return None
|
|
366
|
+
return "gguf" if str(fw).strip().lower() in _GGUF_ENGINES else "transformers"
|
|
367
|
+
|
|
368
|
+
|
|
369
|
+
def _logical_id(key: str) -> str:
|
|
370
|
+
"""Base-name identity of a key with packaging/quant tokens dropped, so the
|
|
371
|
+
GGUF and Transformers rows of one model share it (``…-Coder-Next-GGUF`` and
|
|
372
|
+
``…-Coder-Next`` -> ``coder next``). Used ONLY to pair representation
|
|
373
|
+
siblings under an explicit model_format."""
|
|
374
|
+
return " ".join(t for t in _tokens(_bare_tail(key)) if t not in _FORMAT_TOKENS)
|
|
375
|
+
|
|
376
|
+
|
|
377
|
+
def _owner_of(key: str) -> str:
|
|
378
|
+
"""The publisher qualifier of ``key`` (``Owner~Repo`` / ``owner/repo``), or
|
|
379
|
+
its hub_id owner, or "" — the same owner rule _match_tier enforces, so a
|
|
380
|
+
representation redirect never crosses publishers."""
|
|
381
|
+
s = str(key)
|
|
382
|
+
if "~" in s:
|
|
383
|
+
return _slugify(s.split("~", 1)[0])
|
|
384
|
+
hub_id = getattr(MODEL_REGISTRY.get(key), "hub_id", None)
|
|
385
|
+
if hub_id and "/" in str(hub_id):
|
|
386
|
+
return _slugify(str(hub_id).split("/", 1)[0])
|
|
387
|
+
if "/" in s:
|
|
388
|
+
return _slugify(s.split("/", 1)[0])
|
|
389
|
+
return ""
|
|
390
|
+
|
|
391
|
+
|
|
392
|
+
def _redirect_to_format(resolved: str, model_key: str, fmt: str, *, strict: bool):
|
|
393
|
+
"""Return the key of the requested REPRESENTATION for the model ``resolved``
|
|
394
|
+
names. When ``resolved`` is already that representation it is returned
|
|
395
|
+
unchanged; otherwise the same-logical-identity, same-owner sibling of
|
|
396
|
+
representation ``fmt`` is chosen (deterministically). When the registry has
|
|
397
|
+
no such representation the resolved key is kept and a warning logged — a
|
|
398
|
+
resolver cannot invent a row that is not on record."""
|
|
399
|
+
if not resolved or _representation_of(resolved) == fmt:
|
|
400
|
+
return resolved
|
|
401
|
+
want_logical = _logical_id(resolved)
|
|
402
|
+
want_owner = _owner_of(resolved)
|
|
403
|
+
siblings = [
|
|
404
|
+
k for k in MODEL_REGISTRY
|
|
405
|
+
if _representation_of(k) == fmt
|
|
406
|
+
and _logical_id(k) == want_logical
|
|
407
|
+
and (not want_owner or _owner_of(k) == want_owner)
|
|
408
|
+
]
|
|
409
|
+
if not siblings:
|
|
410
|
+
_log.warning("assure_model_key: model_format=%r requested for %r but no "
|
|
411
|
+
"%s representation of %r is on record; kept %s",
|
|
412
|
+
fmt, model_key, fmt, resolved, resolved)
|
|
413
|
+
return resolved
|
|
414
|
+
if len(siblings) == 1:
|
|
415
|
+
return siblings[0]
|
|
416
|
+
return _pick_by_total_order(siblings, strict=strict) or siblings[0]
|
|
417
|
+
|
|
418
|
+
|
|
323
419
|
def _match_tier(key, values, slug, bare_slug, model_key) -> int:
|
|
324
420
|
"""The strongest identity tier at which ``key`` matches the request, or 0."""
|
|
325
421
|
if _slugify(key) == slug:
|
|
@@ -330,7 +426,26 @@ def _match_tier(key, values, slug, bare_slug, model_key) -> int:
|
|
|
330
426
|
folder = getattr(values, "folder", None)
|
|
331
427
|
if folder and _path_suffix_matches(folder, model_key):
|
|
332
428
|
return _TIER_FOLDER
|
|
429
|
+
# A qualified request names a publisher, not merely a base-name family.
|
|
430
|
+
# Never let the bare-tail tier silently turn an explicit
|
|
431
|
+
# ``unsloth~Qwen3-Coder-Next-GGUF`` request into the Qwen variant (or vice
|
|
432
|
+
# versa) when one qualified registry row is absent. The qualified owner
|
|
433
|
+
# may be represented by the registry key or by the model's Hub ID while
|
|
434
|
+
# the registry key itself remains bare.
|
|
333
435
|
if _slugify(_bare_tail(key)) == bare_slug:
|
|
436
|
+
requested_owner = ""
|
|
437
|
+
if "~" in str(model_key):
|
|
438
|
+
requested_owner = str(model_key).split("~", 1)[0].strip()
|
|
439
|
+
elif "/" in str(model_key):
|
|
440
|
+
requested_owner = str(model_key).split("/", 1)[0].strip()
|
|
441
|
+
if requested_owner:
|
|
442
|
+
candidate_owner = ""
|
|
443
|
+
if "~" in str(key):
|
|
444
|
+
candidate_owner = str(key).split("~", 1)[0].strip()
|
|
445
|
+
elif hub_id and "/" in str(hub_id):
|
|
446
|
+
candidate_owner = str(hub_id).split("/", 1)[0].strip()
|
|
447
|
+
if _slugify(candidate_owner) != _slugify(requested_owner):
|
|
448
|
+
return 0
|
|
334
449
|
return _TIER_BARE
|
|
335
450
|
return 0
|
|
336
451
|
|
|
@@ -351,7 +466,29 @@ def _pick_by_total_order(keys, *, strict):
|
|
|
351
466
|
return ranked[0]
|
|
352
467
|
|
|
353
468
|
|
|
354
|
-
def assure_model_key(model_key, *, strict: bool = False,
|
|
469
|
+
def assure_model_key(model_key, *, strict: bool = False, fmt: str = "auto",
|
|
470
|
+
_expand_aliases: bool = True):
|
|
471
|
+
"""Resolve a model reference to a canonical registry key.
|
|
472
|
+
|
|
473
|
+
``fmt`` (model_format: "auto" | "gguf" | "transformers") is the EXPLICIT
|
|
474
|
+
representation override (operator 2026-09-29). "auto" (default) is
|
|
475
|
+
byte-identical to legacy resolution. "gguf"/"transformers" is authoritative:
|
|
476
|
+
the base resolution runs first, then :func:`_redirect_to_format` swaps in the
|
|
477
|
+
same-model, same-owner sibling of the requested representation (ignoring what
|
|
478
|
+
is currently serving and any contradicting name suffix). The GGUF-vs-
|
|
479
|
+
Transformers special-casing downstream keys off the RESOLVED key's framework,
|
|
480
|
+
so nothing beyond the returned key needs to know about ``fmt``.
|
|
481
|
+
"""
|
|
482
|
+
resolved = _assure_model_key_base(model_key, strict=strict,
|
|
483
|
+
_expand_aliases=_expand_aliases)
|
|
484
|
+
if resolved and fmt and str(fmt).strip().lower() not in ("", "auto"):
|
|
485
|
+
return _redirect_to_format(resolved, model_key, str(fmt).strip().lower(),
|
|
486
|
+
strict=strict)
|
|
487
|
+
return resolved
|
|
488
|
+
|
|
489
|
+
|
|
490
|
+
def _assure_model_key_base(model_key, *, strict: bool = False,
|
|
491
|
+
_expand_aliases: bool = True):
|
|
355
492
|
"""
|
|
356
493
|
Resolve a user-provided model key, repo id, manifest slug, folder name,
|
|
357
494
|
or folder suffix into the canonical key from MODEL_REGISTRY.
|
|
@@ -387,7 +524,7 @@ def assure_model_key(model_key, *, strict: bool = False, _expand_aliases: bool =
|
|
|
387
524
|
if _expand_aliases:
|
|
388
525
|
target = _alias_target(model_key)
|
|
389
526
|
if target and target != model_key:
|
|
390
|
-
resolved =
|
|
527
|
+
resolved = _assure_model_key_base(target, strict=strict, _expand_aliases=False)
|
|
391
528
|
if resolved is not None:
|
|
392
529
|
_log.debug("assure_model_key: alias %r -> %r -> %s",
|
|
393
530
|
model_key, target, resolved)
|
|
@@ -410,6 +547,15 @@ def assure_model_key(model_key, *, strict: bool = False, _expand_aliases: bool =
|
|
|
410
547
|
top = [k for k, t in tiers.items() if t == top_tier]
|
|
411
548
|
if len(top) == 1:
|
|
412
549
|
return top[0]
|
|
550
|
+
# Same-family variants (notably GGUF vs Transformers) are not a real
|
|
551
|
+
# ambiguity when the caller did not name a packaging. Let the normal
|
|
552
|
+
# serving-aware pipeline choose the live representation first. An
|
|
553
|
+
# explicitly qualified request still takes the deterministic exact
|
|
554
|
+
# path below and cannot be redirected across frameworks.
|
|
555
|
+
if not _package_specific(model_key):
|
|
556
|
+
chosen = _pipeline_pick(model_key, top)
|
|
557
|
+
if chosen is not None:
|
|
558
|
+
return chosen
|
|
413
559
|
winner = _pick_by_total_order(top, strict=strict)
|
|
414
560
|
if winner is not None:
|
|
415
561
|
_log.debug("assure_model_key: broke tier-%d tie for %r -> %s (of %s)",
|
|
@@ -79,9 +79,13 @@ def _build_chat_request(kwargs: Dict[str, Any], model_key: str) -> ChatRequest:
|
|
|
79
79
|
# the keys above; without forwarding it here the /v1 route's derived client
|
|
80
80
|
# identity never reaches ChatRequest.caller, so every call records as "api".
|
|
81
81
|
# It is central-only (dropped from model_dump before the worker wire).
|
|
82
|
+
# model_format: central-only representation pin (operator 2026-09-29) — rides
|
|
83
|
+
# ChatRequest so the serving fast-route matches by representation; dropped
|
|
84
|
+
# from the worker wire by ChatRequest's serializer, same as ``caller``.
|
|
82
85
|
for k in ("max_new_tokens", "temperature", "top_p", "do_sample", "request_id",
|
|
83
86
|
"unbounded", "max_chunks", "pool", "images", "alloc",
|
|
84
|
-
"chat_template_kwargs", "logit_bias", "no_makeroom", "caller"
|
|
87
|
+
"chat_template_kwargs", "logit_bias", "no_makeroom", "caller",
|
|
88
|
+
"model_format"):
|
|
85
89
|
if k in kwargs:
|
|
86
90
|
out[k] = kwargs[k]
|
|
87
91
|
out.setdefault("request_id", make_request_id())
|
|
@@ -180,12 +180,16 @@ def _adopt_vl_tasks_from_disk(model_key, cfg, task):
|
|
|
180
180
|
return None
|
|
181
181
|
|
|
182
182
|
|
|
183
|
+
_MODEL_FORMATS = ("auto", "gguf", "transformers")
|
|
184
|
+
|
|
185
|
+
|
|
183
186
|
def resolve_model_key(
|
|
184
187
|
*,
|
|
185
188
|
model_key: Optional[str] = None,
|
|
186
189
|
file: Optional[str] = None,
|
|
187
190
|
media_type: Optional[str] = None,
|
|
188
191
|
task: Optional[str] = None,
|
|
192
|
+
model_format: Optional[str] = None,
|
|
189
193
|
) -> str:
|
|
190
194
|
"""Pick a model_key via explicit resolution chain.
|
|
191
195
|
|
|
@@ -194,15 +198,25 @@ def resolve_model_key(
|
|
|
194
198
|
|
|
195
199
|
`task`, when given alongside `model_key`, is validated against
|
|
196
200
|
cfg.tasks. When given alone, it picks TASK_DEFAULTS[task].
|
|
201
|
+
|
|
202
|
+
`model_format` ("auto" | "gguf" | "transformers", default auto) is the
|
|
203
|
+
explicit representation override — authoritative when choosing between the
|
|
204
|
+
GGUF and Transformers rows of the same model (operator 2026-09-29).
|
|
197
205
|
"""
|
|
198
206
|
if task is not None and task not in KNOWN_TASKS_REGISTRY:
|
|
199
207
|
raise KeyError(
|
|
200
208
|
f"Unknown task={task!r}; known: {sorted(KNOWN_TASKS_REGISTRY)}"
|
|
201
209
|
)
|
|
202
|
-
|
|
210
|
+
fmt = (model_format or "auto").strip().lower() if model_format else "auto"
|
|
211
|
+
if fmt not in _MODEL_FORMATS:
|
|
212
|
+
raise ValueError(
|
|
213
|
+
f"Unknown model_format={model_format!r}; expected one of "
|
|
214
|
+
f"{list(_MODEL_FORMATS)}"
|
|
215
|
+
)
|
|
216
|
+
|
|
203
217
|
if model_key is not None:
|
|
204
218
|
requested = model_key
|
|
205
|
-
model_key = assure_model_key(model_key)
|
|
219
|
+
model_key = assure_model_key(model_key, fmt=fmt)
|
|
206
220
|
if not model_key:
|
|
207
221
|
# Fail with NEAR MATCHES, not the whole registry. Two cases produce
|
|
208
222
|
# a None here: nothing matched, or a fuzzy tie the pipeline could
|
|
@@ -349,6 +363,7 @@ def resolve(prompt_kwargs: Dict[str, Any]) -> Resolution:
|
|
|
349
363
|
file=prompt_kwargs.get("file"),
|
|
350
364
|
media_type=prompt_kwargs.get("media_type"),
|
|
351
365
|
task=requested_task,
|
|
366
|
+
model_format=prompt_kwargs.get("model_format"),
|
|
352
367
|
)
|
|
353
368
|
|
|
354
369
|
cfg = MODEL_REGISTRY[model_key]
|