hugpy-engine 0.2.3__tar.gz → 0.2.5__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.3/src/hugpy_engine.egg-info → hugpy_engine-0.2.5}/PKG-INFO +1 -1
- {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/apis/get_module.py +50 -2
- {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/catalog_bridge.py +8 -1
- {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/config/main.py +16 -0
- {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/config/models/model_meta.py +10 -2
- {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/config/models/models_config.py +176 -0
- {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/dispatch/activity.py +14 -2
- {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/dispatch/dispatch.py +20 -3
- hugpy_engine-0.2.5/src/hugpy_engine/fit/__init__.py +28 -0
- hugpy_engine-0.2.5/src/hugpy_engine/fit/flex.py +689 -0
- hugpy_engine-0.2.5/src/hugpy_engine/fit/plan.py +587 -0
- hugpy_engine-0.2.5/src/hugpy_engine/fit/types.py +428 -0
- {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/generate/config.py +77 -6
- {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/llama/runners/get.py +41 -9
- {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/llama/runners/src/base_runner.py +53 -2
- {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/llama/runners/src/ccp_runner.py +15 -0
- {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/llama/runners/src/python_runner.py +35 -0
- {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/peft_adapters.py +9 -2
- {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/resolvers/__init__.py +2 -1
- {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/resolvers/assure_model_key.py +195 -7
- {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/resolvers/categories/builders.py +5 -1
- {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/resolvers/model_resolver.py +17 -2
- {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/resolvers/remote.py +778 -45
- {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/schemas/chat_schemas.py +18 -0
- {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/schemas/event_schemas.py +10 -0
- {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/schemas/metadata_schemas.py +1 -0
- {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/serve/load_failure.py +84 -1
- {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/serve/overrides.py +59 -75
- {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/serve/slot_agent.py +27 -0
- {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/serve/slots.py +169 -114
- {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/spill.py +62 -16
- {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/wire/chat_schemas.py +7 -0
- {hugpy_engine-0.2.3 → hugpy_engine-0.2.5/src/hugpy_engine.egg-info}/PKG-INFO +1 -1
- {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine.egg-info/SOURCES.txt +22 -1
- {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine.egg-info/scm_file_list.json +21 -0
- hugpy_engine-0.2.5/src/hugpy_engine.egg-info/scm_version.json +8 -0
- {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/tests/conftest.py +18 -0
- hugpy_engine-0.2.5/tests/known_good/test_call_queue_contract.py +357 -0
- hugpy_engine-0.2.5/tests/known_good/test_dependency_acquisition_contract.py +252 -0
- hugpy_engine-0.2.5/tests/known_good/test_evict_fit_contract.py +198 -0
- hugpy_engine-0.2.5/tests/known_good/test_key_equivalence_contract.py +99 -0
- hugpy_engine-0.2.5/tests/known_good/test_missing_dependency_contract.py +116 -0
- hugpy_engine-0.2.5/tests/known_good/test_model_format_contract.py +193 -0
- hugpy_engine-0.2.5/tests/known_good/test_model_key_contract.py +318 -0
- hugpy_engine-0.2.5/tests/known_good/test_plan_fit_contract.py +762 -0
- hugpy_engine-0.2.5/tests/known_good/test_politeness_retired_contract.py +65 -0
- hugpy_engine-0.2.5/tests/known_good/test_prefill_progress_contract.py +99 -0
- hugpy_engine-0.2.5/tests/known_good/test_relay_gate_allocation_contract.py +111 -0
- hugpy_engine-0.2.5/tests/known_good/test_relay_stream_stall_contract.py +279 -0
- hugpy_engine-0.2.5/tests/known_good/test_remote_error_structure_contract.py +133 -0
- hugpy_engine-0.2.5/tests/known_good/test_response_format_contract.py +104 -0
- hugpy_engine-0.2.5/tests/known_good/test_slot_seat_admission_contract.py +196 -0
- hugpy_engine-0.2.5/tests/known_good/test_slot_serialization_contract.py +159 -0
- {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/tests/test_central_cap_gating.py +38 -7
- {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/tests/test_chat_role_alternation.py +36 -8
- {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/tests/test_ctx_fit.py +8 -0
- {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/tests/test_hot_cache.py +27 -0
- {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/tests/test_per_request_alloc.py +5 -0
- hugpy_engine-0.2.5/tests/test_slot_agent_loading_state.py +75 -0
- {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/tests/test_spill_ctx_reserve.py +20 -12
- hugpy_engine-0.2.3/src/hugpy_engine.egg-info/scm_version.json +0 -8
- {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/LICENSE +0 -0
- {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/README.md +0 -0
- {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/docs/ALLOCATION-MODES.md +0 -0
- {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/pyproject.toml +0 -0
- {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/setup.cfg +0 -0
- {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/__init__.py +0 -0
- {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/alloc_modes.py +0 -0
- {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/allocation.py +0 -0
- {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/apis/__init__.py +0 -0
- {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/apis/call_api.py +0 -0
- {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/apis/reclassify.py +0 -0
- {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/apis/reconcile.py +0 -0
- {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/apis/serve/__init__.py +0 -0
- {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/apis/serve/serve.py +0 -0
- {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/apis/serve/serve_cli.py +0 -0
- {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/apis/systemd_units.py +0 -0
- {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/backend.py +0 -0
- {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/backends/__init__.py +0 -0
- {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/backends/local.py +0 -0
- {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/catalog.py +0 -0
- {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/categories.py +0 -0
- {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/chat_context/__init__.py +0 -0
- {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/chat_context/chat_context.py +0 -0
- {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/chat_context/context_budget.py +0 -0
- {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/chat_context/unbounded.py +0 -0
- {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/config/__init__.py +0 -0
- {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/config/models/__init__.py +0 -0
- {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/config/models/models_default.py +0 -0
- {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/config/models/models_dict.py +0 -0
- {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/contracts.py +0 -0
- {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/dispatch/__init__.py +0 -0
- {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/dispatch/acquire.py +0 -0
- {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/draft_models.py +0 -0
- {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/engines.py +0 -0
- {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/eviction.py +0 -0
- {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/generate/__init__.py +0 -0
- {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/generate/coder.py +0 -0
- {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/generate/generate_runner.py +0 -0
- {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/gguf_election.py +0 -0
- {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/llama/__init__.py +0 -0
- {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/llama/runners/__init__.py +0 -0
- {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/llama/runners/chat_runner.py +0 -0
- {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/llama/runners/src/__init__.py +0 -0
- {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/llama/runners/src/imports/__init__.py +0 -0
- {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/llama/runners/src/imports/config.py +0 -0
- {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/llama/runners/src/imports/constants.py +0 -0
- {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/llama/runners/src/imports/init_imports.py +0 -0
- {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/llama/runners/src/imports/utils.py +0 -0
- {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/llama/runners/src/shard_server.py +0 -0
- {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/llama/serve.py +0 -0
- {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/manifest.py +0 -0
- {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/model_classifier.py +0 -0
- {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/model_index/__init__.py +0 -0
- {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/model_index/client.py +0 -0
- {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/model_index/query_registry.py +0 -0
- {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/model_index/repositories.py +0 -0
- {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/model_index/service.py +0 -0
- {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/name_match.py +0 -0
- {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/native/__init__.py +0 -0
- {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/native/build.py +0 -0
- {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/native/fetch.py +0 -0
- {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/native/resolve.py +0 -0
- {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/output_repair.py +0 -0
- {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/placement.py +0 -0
- {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/py.typed +0 -0
- {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/query.py +0 -0
- {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/resolvers/allocator.py +0 -0
- {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/resolvers/categories/__init__.py +0 -0
- {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/resolvers/categories/frameworks.py +0 -0
- {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/resolvers/device_placement.py +0 -0
- {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/resolvers/groups.py +0 -0
- {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/resolvers/model_dict_resolver.py +0 -0
- {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/routing_diagnostics.py +0 -0
- {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/runtime.py +0 -0
- {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/schemas/__init__.py +0 -0
- {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/schemas/model_schemas.py +0 -0
- {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/schemas/runner_schemas.py +0 -0
- {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/schemas/task_schemas.py +0 -0
- {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/serve/__init__.py +0 -0
- {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/serve/chunksum_verify.py +0 -0
- {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/serve/hot_cache.py +0 -0
- {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/serve/model_cache.py +0 -0
- {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/serve/policy.py +0 -0
- {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/serve/profiles.py +0 -0
- {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/serve/serve.py +0 -0
- {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/serve/serve_cli.py +0 -0
- {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/serve/supervisor.py +0 -0
- {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/task_deps.py +0 -0
- {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/tasks.py +0 -0
- {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/utils/__init__.py +0 -0
- {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/utils/json_scavenge.py +0 -0
- {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/utils/no_think.py +0 -0
- {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/wire/__init__.py +0 -0
- {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/wire/config_schemas.py +0 -0
- {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/wire/install_schemas.py +0 -0
- {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/wire/model_schemas.py +0 -0
- {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/wire/request_schemas.py +0 -0
- {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/wire/specs_schemas.py +0 -0
- {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine.egg-info/dependency_links.txt +0 -0
- {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine.egg-info/entry_points.txt +0 -0
- {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine.egg-info/requires.txt +0 -0
- {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine.egg-info/top_level.txt +0 -0
- {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/tests/test_admission_gate_and_diagnostics.py +0 -0
- {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/tests/test_allocation_accuracy.py +0 -0
- {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/tests/test_budget_bar_spec.py +0 -0
- {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/tests/test_catalog_bridge.py +0 -0
- {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/tests/test_chat_request_caller.py +0 -0
- {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/tests/test_chat_tool_messages_schema.py +0 -0
- {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/tests/test_chunksum_verify.py +0 -0
- {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/tests/test_comms_foundations.py +0 -0
- {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/tests/test_device_placement.py +0 -0
- {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/tests/test_engine_boundary.py +0 -0
- {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/tests/test_engine_persisted_resolve.py +0 -0
- {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/tests/test_eviction_flow.py +0 -0
- {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/tests/test_feasible_distribution_engine.py +0 -0
- {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/tests/test_gguf_election.py +0 -0
- {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/tests/test_gguf_sharded_effective.py +0 -0
- {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/tests/test_image_task_classification.py +0 -0
- {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/tests/test_import_policy.py +0 -0
- {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/tests/test_load_failure_reason.py +0 -0
- {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/tests/test_mmproj_effective_bytes.py +0 -0
- {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/tests/test_model_aliases.py +0 -0
- {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/tests/test_no_makeroom.py +0 -0
- {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/tests/test_no_think.py +0 -0
- {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/tests/test_peft_adapter_load.py +0 -0
- {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/tests/test_per_device_slot.py +0 -0
- {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/tests/test_placement_defaults.py +0 -0
- {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/tests/test_provision_failure_honesty.py +0 -0
- {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/tests/test_registry_attribution.py +0 -0
- {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/tests/test_registry_symmetry.py +0 -0
- {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/tests/test_resolve_routing_key_reconcile.py +0 -0
- {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/tests/test_runner_registry_heartbeat_starvation.py +0 -0
- {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/tests/test_slot_load_stall_health.py +0 -0
- {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/tests/test_slot_moe_vram_preflight.py +0 -0
- {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/tests/test_slot_placement_projection.py +0 -0
- {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/tests/test_slot_port_guard.py +0 -0
- {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/tests/test_slot_relaunch.py +0 -0
- {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/tests/test_spill_vision_fit.py +0 -0
- {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/tests/test_tasks_registry.py +0 -0
- {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/tests/test_tok_s_plausible_ceiling.py +0 -0
- {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/tests/test_transformers_placement.py +0 -0
- {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/tests/test_video_model_classification.py +0 -0
- {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/tests/test_vision_needs_slot.py +0 -0
- {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/tests/test_vram_cliff.py +0 -0
- {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/tests/test_worker_error_attribution.py +0 -0
- {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/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.5
|
|
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.
|
|
@@ -52,7 +52,14 @@ class EngineCatalogSource:
|
|
|
52
52
|
def canonical_key(self, model_key: str) -> Optional[str]:
|
|
53
53
|
from hugpy_engine.resolvers.assure_model_key import assure_model_key
|
|
54
54
|
|
|
55
|
-
|
|
55
|
+
# IDENTITY resolution only (operator rule 2026-09-29): this is the
|
|
56
|
+
# storage/provision seam a WORKER uses to re-resolve a key central
|
|
57
|
+
# already resolved (ensure_model_registered -> _assure_local_key). The
|
|
58
|
+
# shorthand fuzzy tier must never answer here — on ae it turned
|
|
59
|
+
# ``Qwen3.8-9B-GGUF`` into the resident ``Qwen3.8-9B-Distill-GGUF``
|
|
60
|
+
# (same file, different model) and served it (S3/F7). None means
|
|
61
|
+
# "learn the row from central", never "use the nearest sibling".
|
|
62
|
+
return assure_model_key(model_key, fuzzy=False)
|
|
56
63
|
|
|
57
64
|
def resolve_dir(self, model_key: str) -> Optional[str]:
|
|
58
65
|
from hugpy_engine.config.main import get_model_path
|
|
@@ -50,6 +50,14 @@ def _resolve_model_key(model_key, registry, prefer=None):
|
|
|
50
50
|
def _bare(k): # name without the "owner~" qualifier
|
|
51
51
|
return k.split("~", 1)[1] if "~" in k else k
|
|
52
52
|
candidates = sorted(k for k in registry if _bare(k) == model_key)
|
|
53
|
+
if not candidates:
|
|
54
|
+
# FORMAT EQUIVALENCE (operator rule 2026-09-29): ``X`` and ``X-GGUF``
|
|
55
|
+
# are one key, so either spelling resolves to the row on record under
|
|
56
|
+
# the other. Nothing else is folded — ``X-Distill-GGUF`` is a different
|
|
57
|
+
# model and never answers for ``X-GGUF`` (S3/F7). Lazy import keeps the
|
|
58
|
+
# config module's import graph unchanged.
|
|
59
|
+
from hugpy_engine.fit.types import key_equivalent
|
|
60
|
+
candidates = sorted(k for k in registry if key_equivalent(_bare(k), model_key))
|
|
53
61
|
if not candidates:
|
|
54
62
|
return None
|
|
55
63
|
if len(candidates) == 1:
|
|
@@ -101,6 +109,14 @@ def get_model_path(key: str):
|
|
|
101
109
|
return env_override
|
|
102
110
|
cfg = get_model_config(key)
|
|
103
111
|
path = os.path.join(MODELS_HOME,cfg.folder)
|
|
112
|
+
# A managed copy takes precedence. The worker's external pointer is used
|
|
113
|
+
# only when its Hugpy cache does not hold the model.
|
|
114
|
+
external = (getattr(cfg, "extra", None) or {}).get("external_location")
|
|
115
|
+
if external and not model_looks_downloaded(path, cfg) and (
|
|
116
|
+
os.path.isdir(external) or
|
|
117
|
+
((getattr(cfg, "extra", None) or {}).get("ollama_model") and
|
|
118
|
+
os.path.isfile(external))):
|
|
119
|
+
return external
|
|
104
120
|
# Read-through (2026-07-12 hotfix): cfg.folder is CENTRAL's layout — after
|
|
105
121
|
# the store flattening it names the FLAT dir, but a worker's files may
|
|
106
122
|
# 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
|
|
|
@@ -92,6 +95,13 @@ def snapshot() -> list:
|
|
|
92
95
|
"kind": d["kind"],
|
|
93
96
|
"prompt": d.get("prompt") or "",
|
|
94
97
|
"state": state,
|
|
98
|
+
# WHERE a processing call is (2026-09-29): "prefill" while the
|
|
99
|
+
# engine is still reading the prompt (n_past/n_prompt ride on
|
|
100
|
+
# progress/input_tokens), "" once tokens flow. Without this the
|
|
101
|
+
# queue read "answering, 0 tokens" for a 105 s legitimate prefill.
|
|
102
|
+
"stage": d.get("stage") or "",
|
|
103
|
+
"progress": d.get("progress"),
|
|
104
|
+
"input_tokens": d.get("input_tokens"),
|
|
95
105
|
"elapsed": d["elapsed"],
|
|
96
106
|
"wait": d["wait"],
|
|
97
107
|
"tokens": d["tokens"],
|
|
@@ -105,6 +115,8 @@ def counts() -> dict:
|
|
|
105
115
|
if d["kind"] not in _QUEUE_KINDS_EXCLUDED]
|
|
106
116
|
waiting = sum(1 for d in live
|
|
107
117
|
if _VIEW_STATE.get(d["status"]) == "waiting")
|
|
118
|
+
# Preserve the existing API shape (`active` is the non-waiting bucket),
|
|
119
|
+
# while snapshots distinguish `processing` from token-producing `active`.
|
|
108
120
|
active = sum(1 for d in live
|
|
109
|
-
if _VIEW_STATE.get(d["status"])
|
|
121
|
+
if _VIEW_STATE.get(d["status"]) in ("processing", "active"))
|
|
110
122
|
return {"waiting": waiting, "active": active, "total": waiting + active}
|
|
@@ -697,7 +697,25 @@ async def stream_runner(runner, req, cancel_event=None):
|
|
|
697
697
|
timings=getattr(result, "timings", None))
|
|
698
698
|
else:
|
|
699
699
|
yield ErrorEvent(request_id=req.request_id,
|
|
700
|
-
message=getattr(result, "error", None) or "run failed"
|
|
700
|
+
message=getattr(result, "error", None) or "run failed",
|
|
701
|
+
load_failure=(getattr(result, "load_failure", None)
|
|
702
|
+
if isinstance(getattr(result, "load_failure", None), dict)
|
|
703
|
+
else None))
|
|
704
|
+
|
|
705
|
+
|
|
706
|
+
def _rewrap_error_event(event, rid):
|
|
707
|
+
"""The ONE re-wrap of an inner ErrorEvent under the outer request id
|
|
708
|
+
(S5, 2026-09-29). It used to rebuild the event from ``message`` alone, so
|
|
709
|
+
the worker's structured ``load_failure`` (class + fit_failure + refusal —
|
|
710
|
+
F3) and ``code`` were dropped HERE, on central, between the relay and the
|
|
711
|
+
/v1 envelope: the client saw ``type: api_error`` with no ``fit_failure``
|
|
712
|
+
although the worker had logged kind=vram_fit. Carry both."""
|
|
713
|
+
return ErrorEvent(request_id=rid,
|
|
714
|
+
message=getattr(event, "message", None) or "run failed",
|
|
715
|
+
load_failure=(getattr(event, "load_failure", None)
|
|
716
|
+
if isinstance(getattr(event, "load_failure", None), dict)
|
|
717
|
+
else None),
|
|
718
|
+
code=(str(getattr(event, "code")) if getattr(event, "code", None) else None))
|
|
701
719
|
|
|
702
720
|
|
|
703
721
|
async def execute_prompt_stream(*args, cancel_event=None, **kwargs):
|
|
@@ -948,8 +966,7 @@ async def execute_chat_stream(*args, cancel_event=None, **kwargs):
|
|
|
948
966
|
output_chunks=1, finish_reason="stop",
|
|
949
967
|
usage=usage_totals, timings=timings_last)
|
|
950
968
|
else:
|
|
951
|
-
yield
|
|
952
|
-
message=getattr(event, "message", None) or "run failed")
|
|
969
|
+
yield _rewrap_error_event(event, rid)
|
|
953
970
|
errored = True
|
|
954
971
|
break
|
|
955
972
|
else:
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"""``hugpy_engine.fit`` — the pure, deterministic evict-to-fit core.
|
|
2
|
+
|
|
3
|
+
Standalone: imports only ``hugpy_engine`` siblings (``eviction``, ``spill``)
|
|
4
|
+
and the standard library — no torch, no nvml, no fleet. See ``plan.py``.
|
|
5
|
+
"""
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
from hugpy_engine.fit.flex import ( # noqa: F401
|
|
9
|
+
FlexPlan, PartialPlan, band_bounds, band_ceiling, band_floor,
|
|
10
|
+
ctx_band_bounds, flex_priority_key, kv_at_ctx_pct, leniency_floor_pct,
|
|
11
|
+
plan_explicit_offload, plan_flex, plan_partial_offload,
|
|
12
|
+
)
|
|
13
|
+
from hugpy_engine.fit.plan import evict_order, need_split, plan_fit # noqa: F401
|
|
14
|
+
from hugpy_engine.fit.types import ( # noqa: F401
|
|
15
|
+
FAILURE_KINDS, DeviceVram, Eviction, FitFailure, FitPlan, FitPolicy,
|
|
16
|
+
FitRequest, MoeSplit, Resident, ResourceSnapshot, VramSnapshot,
|
|
17
|
+
canonical_key, key_equivalent,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
__all__ = [
|
|
21
|
+
"plan_fit", "evict_order", "need_split",
|
|
22
|
+
"ResourceSnapshot", "VramSnapshot", "DeviceVram", "FitPolicy", "Resident",
|
|
23
|
+
"FitRequest", "Eviction", "MoeSplit", "FitFailure", "FAILURE_KINDS", "FitPlan",
|
|
24
|
+
"canonical_key", "key_equivalent",
|
|
25
|
+
"FlexPlan", "PartialPlan", "band_bounds", "band_ceiling", "band_floor",
|
|
26
|
+
"ctx_band_bounds", "flex_priority_key", "kv_at_ctx_pct", "leniency_floor_pct",
|
|
27
|
+
"plan_explicit_offload", "plan_flex", "plan_partial_offload",
|
|
28
|
+
]
|