hugpy-engine 0.2.5__tar.gz → 0.2.6__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.5/src/hugpy_engine.egg-info → hugpy_engine-0.2.6}/PKG-INFO +1 -1
- {hugpy_engine-0.2.5 → hugpy_engine-0.2.6}/src/hugpy_engine/config/models/models_config.py +40 -7
- {hugpy_engine-0.2.5 → hugpy_engine-0.2.6}/src/hugpy_engine/dispatch/dispatch.py +28 -1
- {hugpy_engine-0.2.5 → hugpy_engine-0.2.6}/src/hugpy_engine/fit/flex.py +27 -7
- hugpy_engine-0.2.6/src/hugpy_engine/fit/gguf_need.py +203 -0
- {hugpy_engine-0.2.5 → hugpy_engine-0.2.6}/src/hugpy_engine/fit/plan.py +197 -15
- {hugpy_engine-0.2.5 → hugpy_engine-0.2.6}/src/hugpy_engine/fit/types.py +34 -4
- {hugpy_engine-0.2.5 → hugpy_engine-0.2.6}/src/hugpy_engine/generate/coder.py +27 -0
- {hugpy_engine-0.2.5 → hugpy_engine-0.2.6}/src/hugpy_engine/generate/generate_runner.py +50 -0
- {hugpy_engine-0.2.5 → hugpy_engine-0.2.6}/src/hugpy_engine/llama/runners/chat_runner.py +21 -0
- {hugpy_engine-0.2.5 → hugpy_engine-0.2.6}/src/hugpy_engine/llama/runners/get.py +48 -0
- {hugpy_engine-0.2.5 → hugpy_engine-0.2.6}/src/hugpy_engine/llama/runners/src/ccp_runner.py +5 -1
- {hugpy_engine-0.2.5 → hugpy_engine-0.2.6}/src/hugpy_engine/model_index/__init__.py +27 -1
- hugpy_engine-0.2.6/src/hugpy_engine/model_index/planner/__init__.py +5 -0
- hugpy_engine-0.2.6/src/hugpy_engine/model_index/planner/compute.py +714 -0
- hugpy_engine-0.2.6/src/hugpy_engine/model_index/planner/store.py +466 -0
- hugpy_engine-0.2.6/src/hugpy_engine/model_index/query_registry.py +1052 -0
- {hugpy_engine-0.2.5 → hugpy_engine-0.2.6}/src/hugpy_engine/model_index/repositories.py +50 -8
- hugpy_engine-0.2.6/src/hugpy_engine/model_index/service.py +1025 -0
- {hugpy_engine-0.2.5 → hugpy_engine-0.2.6}/src/hugpy_engine/serve/overrides.py +98 -1
- {hugpy_engine-0.2.5 → hugpy_engine-0.2.6}/src/hugpy_engine/serve/profiles.py +70 -17
- {hugpy_engine-0.2.5 → hugpy_engine-0.2.6}/src/hugpy_engine/serve/slot_agent.py +175 -20
- {hugpy_engine-0.2.5 → hugpy_engine-0.2.6}/src/hugpy_engine/serve/slots.py +32 -4
- hugpy_engine-0.2.6/src/hugpy_engine/serve/tf_child.py +553 -0
- {hugpy_engine-0.2.5 → hugpy_engine-0.2.6}/src/hugpy_engine/spill.py +121 -4
- {hugpy_engine-0.2.5 → hugpy_engine-0.2.6/src/hugpy_engine.egg-info}/PKG-INFO +1 -1
- {hugpy_engine-0.2.5 → hugpy_engine-0.2.6}/src/hugpy_engine.egg-info/SOURCES.txt +16 -0
- {hugpy_engine-0.2.5 → hugpy_engine-0.2.6}/src/hugpy_engine.egg-info/scm_file_list.json +16 -0
- hugpy_engine-0.2.6/src/hugpy_engine.egg-info/scm_version.json +8 -0
- {hugpy_engine-0.2.5 → hugpy_engine-0.2.6}/tests/known_good/test_plan_fit_contract.py +4 -1
- hugpy_engine-0.2.6/tests/test_ctx_range_flex.py +31 -0
- hugpy_engine-0.2.6/tests/test_evict_releases_transformers_registry.py +51 -0
- hugpy_engine-0.2.6/tests/test_planner_encroachment.py +39 -0
- hugpy_engine-0.2.6/tests/test_planner_worker_totals.py +20 -0
- hugpy_engine-0.2.6/tests/test_profile_worker_base.py +25 -0
- hugpy_engine-0.2.6/tests/test_ram_residents_plan.py +50 -0
- hugpy_engine-0.2.6/tests/test_slot_parallel_kv.py +37 -0
- hugpy_engine-0.2.6/tests/test_slot_served_ctx.py +24 -0
- hugpy_engine-0.2.6/tests/test_tf_child.py +111 -0
- hugpy_engine-0.2.6/tests/test_tf_slot_and_routing.py +57 -0
- hugpy_engine-0.2.6/tests/test_transformers_kv_geometry_hybrid_vl.py +30 -0
- hugpy_engine-0.2.5/src/hugpy_engine/model_index/query_registry.py +0 -399
- hugpy_engine-0.2.5/src/hugpy_engine/model_index/service.py +0 -281
- hugpy_engine-0.2.5/src/hugpy_engine.egg-info/scm_version.json +0 -8
- {hugpy_engine-0.2.5 → hugpy_engine-0.2.6}/LICENSE +0 -0
- {hugpy_engine-0.2.5 → hugpy_engine-0.2.6}/README.md +0 -0
- {hugpy_engine-0.2.5 → hugpy_engine-0.2.6}/docs/ALLOCATION-MODES.md +0 -0
- {hugpy_engine-0.2.5 → hugpy_engine-0.2.6}/pyproject.toml +0 -0
- {hugpy_engine-0.2.5 → hugpy_engine-0.2.6}/setup.cfg +0 -0
- {hugpy_engine-0.2.5 → hugpy_engine-0.2.6}/src/hugpy_engine/__init__.py +0 -0
- {hugpy_engine-0.2.5 → hugpy_engine-0.2.6}/src/hugpy_engine/alloc_modes.py +0 -0
- {hugpy_engine-0.2.5 → hugpy_engine-0.2.6}/src/hugpy_engine/allocation.py +0 -0
- {hugpy_engine-0.2.5 → hugpy_engine-0.2.6}/src/hugpy_engine/apis/__init__.py +0 -0
- {hugpy_engine-0.2.5 → hugpy_engine-0.2.6}/src/hugpy_engine/apis/call_api.py +0 -0
- {hugpy_engine-0.2.5 → hugpy_engine-0.2.6}/src/hugpy_engine/apis/get_module.py +0 -0
- {hugpy_engine-0.2.5 → hugpy_engine-0.2.6}/src/hugpy_engine/apis/reclassify.py +0 -0
- {hugpy_engine-0.2.5 → hugpy_engine-0.2.6}/src/hugpy_engine/apis/reconcile.py +0 -0
- {hugpy_engine-0.2.5 → hugpy_engine-0.2.6}/src/hugpy_engine/apis/serve/__init__.py +0 -0
- {hugpy_engine-0.2.5 → hugpy_engine-0.2.6}/src/hugpy_engine/apis/serve/serve.py +0 -0
- {hugpy_engine-0.2.5 → hugpy_engine-0.2.6}/src/hugpy_engine/apis/serve/serve_cli.py +0 -0
- {hugpy_engine-0.2.5 → hugpy_engine-0.2.6}/src/hugpy_engine/apis/systemd_units.py +0 -0
- {hugpy_engine-0.2.5 → hugpy_engine-0.2.6}/src/hugpy_engine/backend.py +0 -0
- {hugpy_engine-0.2.5 → hugpy_engine-0.2.6}/src/hugpy_engine/backends/__init__.py +0 -0
- {hugpy_engine-0.2.5 → hugpy_engine-0.2.6}/src/hugpy_engine/backends/local.py +0 -0
- {hugpy_engine-0.2.5 → hugpy_engine-0.2.6}/src/hugpy_engine/catalog.py +0 -0
- {hugpy_engine-0.2.5 → hugpy_engine-0.2.6}/src/hugpy_engine/catalog_bridge.py +0 -0
- {hugpy_engine-0.2.5 → hugpy_engine-0.2.6}/src/hugpy_engine/categories.py +0 -0
- {hugpy_engine-0.2.5 → hugpy_engine-0.2.6}/src/hugpy_engine/chat_context/__init__.py +0 -0
- {hugpy_engine-0.2.5 → hugpy_engine-0.2.6}/src/hugpy_engine/chat_context/chat_context.py +0 -0
- {hugpy_engine-0.2.5 → hugpy_engine-0.2.6}/src/hugpy_engine/chat_context/context_budget.py +0 -0
- {hugpy_engine-0.2.5 → hugpy_engine-0.2.6}/src/hugpy_engine/chat_context/unbounded.py +0 -0
- {hugpy_engine-0.2.5 → hugpy_engine-0.2.6}/src/hugpy_engine/config/__init__.py +0 -0
- {hugpy_engine-0.2.5 → hugpy_engine-0.2.6}/src/hugpy_engine/config/main.py +0 -0
- {hugpy_engine-0.2.5 → hugpy_engine-0.2.6}/src/hugpy_engine/config/models/__init__.py +0 -0
- {hugpy_engine-0.2.5 → hugpy_engine-0.2.6}/src/hugpy_engine/config/models/model_meta.py +0 -0
- {hugpy_engine-0.2.5 → hugpy_engine-0.2.6}/src/hugpy_engine/config/models/models_default.py +0 -0
- {hugpy_engine-0.2.5 → hugpy_engine-0.2.6}/src/hugpy_engine/config/models/models_dict.py +0 -0
- {hugpy_engine-0.2.5 → hugpy_engine-0.2.6}/src/hugpy_engine/contracts.py +0 -0
- {hugpy_engine-0.2.5 → hugpy_engine-0.2.6}/src/hugpy_engine/dispatch/__init__.py +0 -0
- {hugpy_engine-0.2.5 → hugpy_engine-0.2.6}/src/hugpy_engine/dispatch/acquire.py +0 -0
- {hugpy_engine-0.2.5 → hugpy_engine-0.2.6}/src/hugpy_engine/dispatch/activity.py +0 -0
- {hugpy_engine-0.2.5 → hugpy_engine-0.2.6}/src/hugpy_engine/draft_models.py +0 -0
- {hugpy_engine-0.2.5 → hugpy_engine-0.2.6}/src/hugpy_engine/engines.py +0 -0
- {hugpy_engine-0.2.5 → hugpy_engine-0.2.6}/src/hugpy_engine/eviction.py +0 -0
- {hugpy_engine-0.2.5 → hugpy_engine-0.2.6}/src/hugpy_engine/fit/__init__.py +0 -0
- {hugpy_engine-0.2.5 → hugpy_engine-0.2.6}/src/hugpy_engine/generate/__init__.py +0 -0
- {hugpy_engine-0.2.5 → hugpy_engine-0.2.6}/src/hugpy_engine/generate/config.py +0 -0
- {hugpy_engine-0.2.5 → hugpy_engine-0.2.6}/src/hugpy_engine/gguf_election.py +0 -0
- {hugpy_engine-0.2.5 → hugpy_engine-0.2.6}/src/hugpy_engine/llama/__init__.py +0 -0
- {hugpy_engine-0.2.5 → hugpy_engine-0.2.6}/src/hugpy_engine/llama/runners/__init__.py +0 -0
- {hugpy_engine-0.2.5 → hugpy_engine-0.2.6}/src/hugpy_engine/llama/runners/src/__init__.py +0 -0
- {hugpy_engine-0.2.5 → hugpy_engine-0.2.6}/src/hugpy_engine/llama/runners/src/base_runner.py +0 -0
- {hugpy_engine-0.2.5 → hugpy_engine-0.2.6}/src/hugpy_engine/llama/runners/src/imports/__init__.py +0 -0
- {hugpy_engine-0.2.5 → hugpy_engine-0.2.6}/src/hugpy_engine/llama/runners/src/imports/config.py +0 -0
- {hugpy_engine-0.2.5 → hugpy_engine-0.2.6}/src/hugpy_engine/llama/runners/src/imports/constants.py +0 -0
- {hugpy_engine-0.2.5 → hugpy_engine-0.2.6}/src/hugpy_engine/llama/runners/src/imports/init_imports.py +0 -0
- {hugpy_engine-0.2.5 → hugpy_engine-0.2.6}/src/hugpy_engine/llama/runners/src/imports/utils.py +0 -0
- {hugpy_engine-0.2.5 → hugpy_engine-0.2.6}/src/hugpy_engine/llama/runners/src/python_runner.py +0 -0
- {hugpy_engine-0.2.5 → hugpy_engine-0.2.6}/src/hugpy_engine/llama/runners/src/shard_server.py +0 -0
- {hugpy_engine-0.2.5 → hugpy_engine-0.2.6}/src/hugpy_engine/llama/serve.py +0 -0
- {hugpy_engine-0.2.5 → hugpy_engine-0.2.6}/src/hugpy_engine/manifest.py +0 -0
- {hugpy_engine-0.2.5 → hugpy_engine-0.2.6}/src/hugpy_engine/model_classifier.py +0 -0
- {hugpy_engine-0.2.5 → hugpy_engine-0.2.6}/src/hugpy_engine/model_index/client.py +0 -0
- {hugpy_engine-0.2.5 → hugpy_engine-0.2.6}/src/hugpy_engine/name_match.py +0 -0
- {hugpy_engine-0.2.5 → hugpy_engine-0.2.6}/src/hugpy_engine/native/__init__.py +0 -0
- {hugpy_engine-0.2.5 → hugpy_engine-0.2.6}/src/hugpy_engine/native/build.py +0 -0
- {hugpy_engine-0.2.5 → hugpy_engine-0.2.6}/src/hugpy_engine/native/fetch.py +0 -0
- {hugpy_engine-0.2.5 → hugpy_engine-0.2.6}/src/hugpy_engine/native/resolve.py +0 -0
- {hugpy_engine-0.2.5 → hugpy_engine-0.2.6}/src/hugpy_engine/output_repair.py +0 -0
- {hugpy_engine-0.2.5 → hugpy_engine-0.2.6}/src/hugpy_engine/peft_adapters.py +0 -0
- {hugpy_engine-0.2.5 → hugpy_engine-0.2.6}/src/hugpy_engine/placement.py +0 -0
- {hugpy_engine-0.2.5 → hugpy_engine-0.2.6}/src/hugpy_engine/py.typed +0 -0
- {hugpy_engine-0.2.5 → hugpy_engine-0.2.6}/src/hugpy_engine/query.py +0 -0
- {hugpy_engine-0.2.5 → hugpy_engine-0.2.6}/src/hugpy_engine/resolvers/__init__.py +0 -0
- {hugpy_engine-0.2.5 → hugpy_engine-0.2.6}/src/hugpy_engine/resolvers/allocator.py +0 -0
- {hugpy_engine-0.2.5 → hugpy_engine-0.2.6}/src/hugpy_engine/resolvers/assure_model_key.py +0 -0
- {hugpy_engine-0.2.5 → hugpy_engine-0.2.6}/src/hugpy_engine/resolvers/categories/__init__.py +0 -0
- {hugpy_engine-0.2.5 → hugpy_engine-0.2.6}/src/hugpy_engine/resolvers/categories/builders.py +0 -0
- {hugpy_engine-0.2.5 → hugpy_engine-0.2.6}/src/hugpy_engine/resolvers/categories/frameworks.py +0 -0
- {hugpy_engine-0.2.5 → hugpy_engine-0.2.6}/src/hugpy_engine/resolvers/device_placement.py +0 -0
- {hugpy_engine-0.2.5 → hugpy_engine-0.2.6}/src/hugpy_engine/resolvers/groups.py +0 -0
- {hugpy_engine-0.2.5 → hugpy_engine-0.2.6}/src/hugpy_engine/resolvers/model_dict_resolver.py +0 -0
- {hugpy_engine-0.2.5 → hugpy_engine-0.2.6}/src/hugpy_engine/resolvers/model_resolver.py +0 -0
- {hugpy_engine-0.2.5 → hugpy_engine-0.2.6}/src/hugpy_engine/resolvers/remote.py +0 -0
- {hugpy_engine-0.2.5 → hugpy_engine-0.2.6}/src/hugpy_engine/routing_diagnostics.py +0 -0
- {hugpy_engine-0.2.5 → hugpy_engine-0.2.6}/src/hugpy_engine/runtime.py +0 -0
- {hugpy_engine-0.2.5 → hugpy_engine-0.2.6}/src/hugpy_engine/schemas/__init__.py +0 -0
- {hugpy_engine-0.2.5 → hugpy_engine-0.2.6}/src/hugpy_engine/schemas/chat_schemas.py +0 -0
- {hugpy_engine-0.2.5 → hugpy_engine-0.2.6}/src/hugpy_engine/schemas/event_schemas.py +0 -0
- {hugpy_engine-0.2.5 → hugpy_engine-0.2.6}/src/hugpy_engine/schemas/metadata_schemas.py +0 -0
- {hugpy_engine-0.2.5 → hugpy_engine-0.2.6}/src/hugpy_engine/schemas/model_schemas.py +0 -0
- {hugpy_engine-0.2.5 → hugpy_engine-0.2.6}/src/hugpy_engine/schemas/runner_schemas.py +0 -0
- {hugpy_engine-0.2.5 → hugpy_engine-0.2.6}/src/hugpy_engine/schemas/task_schemas.py +0 -0
- {hugpy_engine-0.2.5 → hugpy_engine-0.2.6}/src/hugpy_engine/serve/__init__.py +0 -0
- {hugpy_engine-0.2.5 → hugpy_engine-0.2.6}/src/hugpy_engine/serve/chunksum_verify.py +0 -0
- {hugpy_engine-0.2.5 → hugpy_engine-0.2.6}/src/hugpy_engine/serve/hot_cache.py +0 -0
- {hugpy_engine-0.2.5 → hugpy_engine-0.2.6}/src/hugpy_engine/serve/load_failure.py +0 -0
- {hugpy_engine-0.2.5 → hugpy_engine-0.2.6}/src/hugpy_engine/serve/model_cache.py +0 -0
- {hugpy_engine-0.2.5 → hugpy_engine-0.2.6}/src/hugpy_engine/serve/policy.py +0 -0
- {hugpy_engine-0.2.5 → hugpy_engine-0.2.6}/src/hugpy_engine/serve/serve.py +0 -0
- {hugpy_engine-0.2.5 → hugpy_engine-0.2.6}/src/hugpy_engine/serve/serve_cli.py +0 -0
- {hugpy_engine-0.2.5 → hugpy_engine-0.2.6}/src/hugpy_engine/serve/supervisor.py +0 -0
- {hugpy_engine-0.2.5 → hugpy_engine-0.2.6}/src/hugpy_engine/task_deps.py +0 -0
- {hugpy_engine-0.2.5 → hugpy_engine-0.2.6}/src/hugpy_engine/tasks.py +0 -0
- {hugpy_engine-0.2.5 → hugpy_engine-0.2.6}/src/hugpy_engine/utils/__init__.py +0 -0
- {hugpy_engine-0.2.5 → hugpy_engine-0.2.6}/src/hugpy_engine/utils/json_scavenge.py +0 -0
- {hugpy_engine-0.2.5 → hugpy_engine-0.2.6}/src/hugpy_engine/utils/no_think.py +0 -0
- {hugpy_engine-0.2.5 → hugpy_engine-0.2.6}/src/hugpy_engine/wire/__init__.py +0 -0
- {hugpy_engine-0.2.5 → hugpy_engine-0.2.6}/src/hugpy_engine/wire/chat_schemas.py +0 -0
- {hugpy_engine-0.2.5 → hugpy_engine-0.2.6}/src/hugpy_engine/wire/config_schemas.py +0 -0
- {hugpy_engine-0.2.5 → hugpy_engine-0.2.6}/src/hugpy_engine/wire/install_schemas.py +0 -0
- {hugpy_engine-0.2.5 → hugpy_engine-0.2.6}/src/hugpy_engine/wire/model_schemas.py +0 -0
- {hugpy_engine-0.2.5 → hugpy_engine-0.2.6}/src/hugpy_engine/wire/request_schemas.py +0 -0
- {hugpy_engine-0.2.5 → hugpy_engine-0.2.6}/src/hugpy_engine/wire/specs_schemas.py +0 -0
- {hugpy_engine-0.2.5 → hugpy_engine-0.2.6}/src/hugpy_engine.egg-info/dependency_links.txt +0 -0
- {hugpy_engine-0.2.5 → hugpy_engine-0.2.6}/src/hugpy_engine.egg-info/entry_points.txt +0 -0
- {hugpy_engine-0.2.5 → hugpy_engine-0.2.6}/src/hugpy_engine.egg-info/requires.txt +0 -0
- {hugpy_engine-0.2.5 → hugpy_engine-0.2.6}/src/hugpy_engine.egg-info/top_level.txt +0 -0
- {hugpy_engine-0.2.5 → hugpy_engine-0.2.6}/tests/conftest.py +0 -0
- {hugpy_engine-0.2.5 → hugpy_engine-0.2.6}/tests/known_good/test_call_queue_contract.py +0 -0
- {hugpy_engine-0.2.5 → hugpy_engine-0.2.6}/tests/known_good/test_dependency_acquisition_contract.py +0 -0
- {hugpy_engine-0.2.5 → hugpy_engine-0.2.6}/tests/known_good/test_evict_fit_contract.py +0 -0
- {hugpy_engine-0.2.5 → hugpy_engine-0.2.6}/tests/known_good/test_key_equivalence_contract.py +0 -0
- {hugpy_engine-0.2.5 → hugpy_engine-0.2.6}/tests/known_good/test_missing_dependency_contract.py +0 -0
- {hugpy_engine-0.2.5 → hugpy_engine-0.2.6}/tests/known_good/test_model_format_contract.py +0 -0
- {hugpy_engine-0.2.5 → hugpy_engine-0.2.6}/tests/known_good/test_model_key_contract.py +0 -0
- {hugpy_engine-0.2.5 → hugpy_engine-0.2.6}/tests/known_good/test_politeness_retired_contract.py +0 -0
- {hugpy_engine-0.2.5 → hugpy_engine-0.2.6}/tests/known_good/test_prefill_progress_contract.py +0 -0
- {hugpy_engine-0.2.5 → hugpy_engine-0.2.6}/tests/known_good/test_relay_gate_allocation_contract.py +0 -0
- {hugpy_engine-0.2.5 → hugpy_engine-0.2.6}/tests/known_good/test_relay_stream_stall_contract.py +0 -0
- {hugpy_engine-0.2.5 → hugpy_engine-0.2.6}/tests/known_good/test_remote_error_structure_contract.py +0 -0
- {hugpy_engine-0.2.5 → hugpy_engine-0.2.6}/tests/known_good/test_response_format_contract.py +0 -0
- {hugpy_engine-0.2.5 → hugpy_engine-0.2.6}/tests/known_good/test_slot_seat_admission_contract.py +0 -0
- {hugpy_engine-0.2.5 → hugpy_engine-0.2.6}/tests/known_good/test_slot_serialization_contract.py +0 -0
- {hugpy_engine-0.2.5 → hugpy_engine-0.2.6}/tests/test_admission_gate_and_diagnostics.py +0 -0
- {hugpy_engine-0.2.5 → hugpy_engine-0.2.6}/tests/test_allocation_accuracy.py +0 -0
- {hugpy_engine-0.2.5 → hugpy_engine-0.2.6}/tests/test_budget_bar_spec.py +0 -0
- {hugpy_engine-0.2.5 → hugpy_engine-0.2.6}/tests/test_catalog_bridge.py +0 -0
- {hugpy_engine-0.2.5 → hugpy_engine-0.2.6}/tests/test_central_cap_gating.py +0 -0
- {hugpy_engine-0.2.5 → hugpy_engine-0.2.6}/tests/test_chat_request_caller.py +0 -0
- {hugpy_engine-0.2.5 → hugpy_engine-0.2.6}/tests/test_chat_role_alternation.py +0 -0
- {hugpy_engine-0.2.5 → hugpy_engine-0.2.6}/tests/test_chat_tool_messages_schema.py +0 -0
- {hugpy_engine-0.2.5 → hugpy_engine-0.2.6}/tests/test_chunksum_verify.py +0 -0
- {hugpy_engine-0.2.5 → hugpy_engine-0.2.6}/tests/test_comms_foundations.py +0 -0
- {hugpy_engine-0.2.5 → hugpy_engine-0.2.6}/tests/test_ctx_fit.py +0 -0
- {hugpy_engine-0.2.5 → hugpy_engine-0.2.6}/tests/test_device_placement.py +0 -0
- {hugpy_engine-0.2.5 → hugpy_engine-0.2.6}/tests/test_engine_boundary.py +0 -0
- {hugpy_engine-0.2.5 → hugpy_engine-0.2.6}/tests/test_engine_persisted_resolve.py +0 -0
- {hugpy_engine-0.2.5 → hugpy_engine-0.2.6}/tests/test_eviction_flow.py +0 -0
- {hugpy_engine-0.2.5 → hugpy_engine-0.2.6}/tests/test_feasible_distribution_engine.py +0 -0
- {hugpy_engine-0.2.5 → hugpy_engine-0.2.6}/tests/test_gguf_election.py +0 -0
- {hugpy_engine-0.2.5 → hugpy_engine-0.2.6}/tests/test_gguf_sharded_effective.py +0 -0
- {hugpy_engine-0.2.5 → hugpy_engine-0.2.6}/tests/test_hot_cache.py +0 -0
- {hugpy_engine-0.2.5 → hugpy_engine-0.2.6}/tests/test_image_task_classification.py +0 -0
- {hugpy_engine-0.2.5 → hugpy_engine-0.2.6}/tests/test_import_policy.py +0 -0
- {hugpy_engine-0.2.5 → hugpy_engine-0.2.6}/tests/test_load_failure_reason.py +0 -0
- {hugpy_engine-0.2.5 → hugpy_engine-0.2.6}/tests/test_mmproj_effective_bytes.py +0 -0
- {hugpy_engine-0.2.5 → hugpy_engine-0.2.6}/tests/test_model_aliases.py +0 -0
- {hugpy_engine-0.2.5 → hugpy_engine-0.2.6}/tests/test_no_makeroom.py +0 -0
- {hugpy_engine-0.2.5 → hugpy_engine-0.2.6}/tests/test_no_think.py +0 -0
- {hugpy_engine-0.2.5 → hugpy_engine-0.2.6}/tests/test_peft_adapter_load.py +0 -0
- {hugpy_engine-0.2.5 → hugpy_engine-0.2.6}/tests/test_per_device_slot.py +0 -0
- {hugpy_engine-0.2.5 → hugpy_engine-0.2.6}/tests/test_per_request_alloc.py +0 -0
- {hugpy_engine-0.2.5 → hugpy_engine-0.2.6}/tests/test_placement_defaults.py +0 -0
- {hugpy_engine-0.2.5 → hugpy_engine-0.2.6}/tests/test_provision_failure_honesty.py +0 -0
- {hugpy_engine-0.2.5 → hugpy_engine-0.2.6}/tests/test_registry_attribution.py +0 -0
- {hugpy_engine-0.2.5 → hugpy_engine-0.2.6}/tests/test_registry_symmetry.py +0 -0
- {hugpy_engine-0.2.5 → hugpy_engine-0.2.6}/tests/test_resolve_routing_key_reconcile.py +0 -0
- {hugpy_engine-0.2.5 → hugpy_engine-0.2.6}/tests/test_runner_registry_heartbeat_starvation.py +0 -0
- {hugpy_engine-0.2.5 → hugpy_engine-0.2.6}/tests/test_slot_agent_loading_state.py +0 -0
- {hugpy_engine-0.2.5 → hugpy_engine-0.2.6}/tests/test_slot_load_stall_health.py +0 -0
- {hugpy_engine-0.2.5 → hugpy_engine-0.2.6}/tests/test_slot_moe_vram_preflight.py +0 -0
- {hugpy_engine-0.2.5 → hugpy_engine-0.2.6}/tests/test_slot_placement_projection.py +0 -0
- {hugpy_engine-0.2.5 → hugpy_engine-0.2.6}/tests/test_slot_port_guard.py +0 -0
- {hugpy_engine-0.2.5 → hugpy_engine-0.2.6}/tests/test_slot_relaunch.py +0 -0
- {hugpy_engine-0.2.5 → hugpy_engine-0.2.6}/tests/test_spill_ctx_reserve.py +0 -0
- {hugpy_engine-0.2.5 → hugpy_engine-0.2.6}/tests/test_spill_vision_fit.py +0 -0
- {hugpy_engine-0.2.5 → hugpy_engine-0.2.6}/tests/test_tasks_registry.py +0 -0
- {hugpy_engine-0.2.5 → hugpy_engine-0.2.6}/tests/test_tok_s_plausible_ceiling.py +0 -0
- {hugpy_engine-0.2.5 → hugpy_engine-0.2.6}/tests/test_transformers_placement.py +0 -0
- {hugpy_engine-0.2.5 → hugpy_engine-0.2.6}/tests/test_video_model_classification.py +0 -0
- {hugpy_engine-0.2.5 → hugpy_engine-0.2.6}/tests/test_vision_needs_slot.py +0 -0
- {hugpy_engine-0.2.5 → hugpy_engine-0.2.6}/tests/test_vram_cliff.py +0 -0
- {hugpy_engine-0.2.5 → hugpy_engine-0.2.6}/tests/test_worker_error_attribution.py +0 -0
- {hugpy_engine-0.2.5 → hugpy_engine-0.2.6}/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.6
|
|
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
|
|
@@ -1618,14 +1618,34 @@ def record_worker_models(worker_id: str, rows: dict) -> list[str]:
|
|
|
1618
1618
|
clean[key]["worker_location"] = location
|
|
1619
1619
|
old = (data.get(worker_id) or {}).get("models")
|
|
1620
1620
|
changed = old != clean
|
|
1621
|
-
|
|
1621
|
+
# DETACH STEP 2 (operator 2026-10-02): the catalog's home is the DB
|
|
1622
|
+
# (model_worker_presence, source 'discovered'); the JSON is written only
|
|
1623
|
+
# with HUGPY_WORKER_CATALOG_JSON=1 — or when the DB write did not land
|
|
1624
|
+
# (no DB / DB down): the JSON is the no-DB fallback the reader uses, so
|
|
1625
|
+
# a no-DB central must still persist its scan there (2026-10-02).
|
|
1626
|
+
db_changed = False
|
|
1627
|
+
db_ok = False
|
|
1628
|
+
try:
|
|
1629
|
+
from hugpy_engine.model_index import record_worker_presence
|
|
1630
|
+
rows = []
|
|
1631
|
+
for key, row in clean.items():
|
|
1632
|
+
r = dict(row); r["model_key"] = key
|
|
1633
|
+
if r.get("worker_location"):
|
|
1634
|
+
r["external_location"] = r["worker_location"]
|
|
1635
|
+
rows.append(r)
|
|
1636
|
+
st, det = record_worker_presence(worker_id, rows, source="discovered", complete=True)
|
|
1637
|
+
db_ok = st == "ok"
|
|
1638
|
+
db_changed = bool(db_ok and (det or {}).get("changed"))
|
|
1639
|
+
except Exception: # noqa: BLE001 — a DB miss never breaks a heartbeat
|
|
1640
|
+
db_changed = False
|
|
1641
|
+
if changed and (not db_ok or os.environ.get("HUGPY_WORKER_CATALOG_JSON", "0") == "1"):
|
|
1622
1642
|
data[worker_id] = {"at": time.time(), "models": clean}
|
|
1623
1643
|
tmp = path + f".{os.getpid()}.tmp"
|
|
1624
1644
|
with open(tmp, "w", encoding="utf-8") as fh:
|
|
1625
1645
|
json.dump(data, fh)
|
|
1626
1646
|
os.replace(tmp, path)
|
|
1627
1647
|
fcntl.flock(lock, fcntl.LOCK_UN)
|
|
1628
|
-
_apply_worker_catalog(force=changed)
|
|
1648
|
+
_apply_worker_catalog(force=(changed or db_changed))
|
|
1629
1649
|
# The canonical keys may differ from worker-local scan keys when central
|
|
1630
1650
|
# already has a same-named model from another hub. Return those identities
|
|
1631
1651
|
# so heartbeat placement uses the same keys as the shared model catalog.
|
|
@@ -1639,14 +1659,27 @@ def _apply_worker_catalog(force=False) -> None:
|
|
|
1639
1659
|
registry_dict = globals().get("MODEL_REGISTRY_DICT")
|
|
1640
1660
|
if registry is None or registry_dict is None:
|
|
1641
1661
|
return # import-time base build
|
|
1662
|
+
data = None
|
|
1663
|
+
# DB first (detach step 2): the discovered catalog lives in
|
|
1664
|
+
# model_worker_presence; the JSON file is the no-DB fallback only.
|
|
1642
1665
|
try:
|
|
1643
|
-
|
|
1666
|
+
from hugpy_engine.model_index import fetch_presence_catalog
|
|
1667
|
+
data = fetch_presence_catalog()
|
|
1668
|
+
except Exception: # noqa: BLE001
|
|
1669
|
+
data = None
|
|
1670
|
+
if data is not None:
|
|
1671
|
+
stamp = hash(json.dumps(data, sort_keys=True, default=str))
|
|
1644
1672
|
if not force and stamp == _WORKER_CATALOG_SEEN:
|
|
1645
1673
|
return
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1674
|
+
else:
|
|
1675
|
+
try:
|
|
1676
|
+
stamp = os.stat(_WORKER_CATALOG_PATH).st_mtime_ns
|
|
1677
|
+
if not force and stamp == _WORKER_CATALOG_SEEN:
|
|
1678
|
+
return
|
|
1679
|
+
with open(_WORKER_CATALOG_PATH, encoding="utf-8") as fh:
|
|
1680
|
+
data = json.load(fh)
|
|
1681
|
+
except (OSError, ValueError):
|
|
1682
|
+
return
|
|
1650
1683
|
locations: dict[str, dict[str, str]] = {}
|
|
1651
1684
|
external_apis: dict[str, dict[str, dict]] = {}
|
|
1652
1685
|
candidates: dict[str, dict] = {}
|
|
@@ -1147,7 +1147,26 @@ def evict(model_key: str, task: Optional[str] = None) -> bool:
|
|
|
1147
1147
|
heavy = evict_llama_runner(model_key)
|
|
1148
1148
|
except Exception:
|
|
1149
1149
|
heavy = False
|
|
1150
|
-
|
|
1150
|
+
# TRANSFORMERS weights live in generate.coder.REGISTRY, not in the runner
|
|
1151
|
+
# wrapper: drop them too, then reclaim so the freed VRAM is visible to the
|
|
1152
|
+
# very next fit check (2026-10-02: an in-process eviction "freed 16 MB" of
|
|
1153
|
+
# a 17 GB model and stranded the rest on the card).
|
|
1154
|
+
tf = 0
|
|
1155
|
+
try:
|
|
1156
|
+
from hugpy_engine.generate.coder import REGISTRY as _TF_REGISTRY
|
|
1157
|
+
tf = _TF_REGISTRY.evict_model(model_key)
|
|
1158
|
+
except Exception:
|
|
1159
|
+
tf = 0
|
|
1160
|
+
if tf:
|
|
1161
|
+
try:
|
|
1162
|
+
import gc
|
|
1163
|
+
gc.collect()
|
|
1164
|
+
import torch
|
|
1165
|
+
if torch.cuda.is_available():
|
|
1166
|
+
torch.cuda.empty_cache()
|
|
1167
|
+
except Exception:
|
|
1168
|
+
pass
|
|
1169
|
+
return dropped or heavy or bool(tf)
|
|
1151
1170
|
|
|
1152
1171
|
|
|
1153
1172
|
def clear() -> None:
|
|
@@ -1159,6 +1178,14 @@ def clear() -> None:
|
|
|
1159
1178
|
clear_llama_runners()
|
|
1160
1179
|
except Exception:
|
|
1161
1180
|
pass
|
|
1181
|
+
try:
|
|
1182
|
+
from hugpy_engine.generate.coder import REGISTRY as _TF_REGISTRY
|
|
1183
|
+
for _k in {getattr(getattr(i, "cfg", None), "model_key", None) for i in list(_TF_REGISTRY._instances.values())}:
|
|
1184
|
+
if _k:
|
|
1185
|
+
_TF_REGISTRY.evict_model(_k)
|
|
1186
|
+
_TF_REGISTRY.clear()
|
|
1187
|
+
except Exception:
|
|
1188
|
+
pass
|
|
1162
1189
|
|
|
1163
1190
|
|
|
1164
1191
|
def supported_task_keys() -> List[Tuple[str, str]]:
|
|
@@ -127,7 +127,8 @@ def band_ceiling(target: float, deviation_pct: Optional[float],
|
|
|
127
127
|
|
|
128
128
|
# ── band math (ctx%: the 1..100 point scale) ─────────────────────────────────
|
|
129
129
|
def ctx_band_bounds(ctx_pct: Optional[int],
|
|
130
|
-
deviation_pct: Optional[float]
|
|
130
|
+
deviation_pct: Optional[float],
|
|
131
|
+
floor_pct: Optional[int] = None) -> "tuple[int, int] | None":
|
|
131
132
|
"""The hard ctx band ``(floor_pct, ceil_pct)`` in whole percent points, or
|
|
132
133
|
None when there is no ctx target (ctx band is opt-in — no target == today's
|
|
133
134
|
default ctx, byte-identical).
|
|
@@ -143,6 +144,15 @@ def ctx_band_bounds(ctx_pct: Optional[int],
|
|
|
143
144
|
except (TypeError, ValueError):
|
|
144
145
|
return None
|
|
145
146
|
c = max(1, min(100, c))
|
|
147
|
+
# CONTEXT RANGE (operator 2026-10-02): an explicit minimum makes the band
|
|
148
|
+
# [min, target] — a polite window below the target, never above it.
|
|
149
|
+
if floor_pct is not None:
|
|
150
|
+
try:
|
|
151
|
+
f = int(floor_pct)
|
|
152
|
+
except (TypeError, ValueError):
|
|
153
|
+
f = None
|
|
154
|
+
if f is not None:
|
|
155
|
+
return max(1, min(c, f)), c
|
|
146
156
|
dev = float(deviation_pct or 0.0)
|
|
147
157
|
if dev <= 0:
|
|
148
158
|
return c, c
|
|
@@ -610,17 +620,27 @@ def plan_flex(subject: dict, residents: list, deficit_bytes: int) -> FlexPlan:
|
|
|
610
620
|
subj_ctx = subject.get("ctx_pct")
|
|
611
621
|
subj_dev = subject.get("ctx_deviation_pct")
|
|
612
622
|
subj_kv = int(subject.get("kv_bytes") or 0)
|
|
613
|
-
band = ctx_band_bounds(subj_ctx, subj_dev)
|
|
623
|
+
band = ctx_band_bounds(subj_ctx, subj_dev, subject.get("ctx_floor_pct"))
|
|
614
624
|
if band is not None and subj_kv > 0:
|
|
615
625
|
floor_pct, _ceil = band
|
|
616
626
|
if floor_pct < int(subj_ctx):
|
|
617
|
-
|
|
618
|
-
|
|
627
|
+
c = int(subj_ctx)
|
|
628
|
+
to_pct = floor_pct
|
|
629
|
+
if subject.get("ctx_floor_pct") is not None:
|
|
630
|
+
# An explicit RANGE: the LARGEST ctx in [min, target) whose KV
|
|
631
|
+
# saving covers the deficit ("adjusting to fit where possible",
|
|
632
|
+
# 2026-10-02) — never deeper than needed; the minimum when even
|
|
633
|
+
# it is not enough. A ± deviation band keeps its floor jump.
|
|
634
|
+
keep = subj_kv - remaining
|
|
635
|
+
fit_pct = int(c * keep // subj_kv) if keep > 0 else 0
|
|
636
|
+
to_pct = max(floor_pct, min(c - 1, fit_pct))
|
|
637
|
+
kv_to = kv_at_ctx_pct(subj_kv, c, to_pct)
|
|
638
|
+
saved = max(0, subj_kv - kv_to)
|
|
619
639
|
if saved > 0:
|
|
620
|
-
self_ctx_pct =
|
|
640
|
+
self_ctx_pct = to_pct
|
|
621
641
|
remaining -= saved
|
|
622
642
|
note_parts.append(
|
|
623
|
-
f"self ctx {subj_ctx}%->{
|
|
643
|
+
f"self ctx {subj_ctx}%->{to_pct}% freed {saved}B")
|
|
624
644
|
|
|
625
645
|
if remaining <= 0:
|
|
626
646
|
return FlexPlan("flex", self_ctx_pct=self_ctx_pct,
|
|
@@ -635,7 +655,7 @@ def plan_flex(subject: dict, residents: list, deficit_bytes: int) -> FlexPlan:
|
|
|
635
655
|
for r in residents:
|
|
636
656
|
if r.get("protected"):
|
|
637
657
|
continue # protection outranks priority, always
|
|
638
|
-
rb = ctx_band_bounds(r.get("ctx_pct"), r.get("ctx_deviation_pct"))
|
|
658
|
+
rb = ctx_band_bounds(r.get("ctx_pct"), r.get("ctx_deviation_pct"), r.get("ctx_floor_pct"))
|
|
639
659
|
rkv = int(r.get("kv_bytes") or 0)
|
|
640
660
|
if rb is None or rkv <= 0:
|
|
641
661
|
continue # nothing to compress
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
"""THE GGUF need function (2026-09-30): what a llama-server load of a GGUF puts
|
|
2
|
+
on the GPU and in host RAM, priced from the file's typed structure
|
|
3
|
+
(``hugpy_storage.gguf_inspect.gguf_structure``) — never a flat file size for a
|
|
4
|
+
MoE, never a ``layers x dense`` KV for a hybrid.
|
|
5
|
+
|
|
6
|
+
One source of truth: the worker's admission (``_incoming_need_detail``), the
|
|
7
|
+
allocation rows, central's placement preflight (``_worker_fit``), the planned
|
|
8
|
+
split on the worker row and the Ctx slider preview all call :func:`gguf_need`.
|
|
9
|
+
|
|
10
|
+
Placement follows llama.cpp exactly:
|
|
11
|
+
* ``--n-gpu-layers L`` puts blocks ``i >= n_block - L`` on the GPU; the
|
|
12
|
+
output tensor goes to the GPU only when ``L > n_block`` (``-1``/all does);
|
|
13
|
+
``token_embd`` is ALWAYS on the CPU (llama.cpp's input device); a tied
|
|
14
|
+
output (no ``output.weight``) duplicates ``token_embd`` onto the output
|
|
15
|
+
device.
|
|
16
|
+
* ``--n-cpu-moe N`` keeps the expert tensors (``ffn_{up,down,gate,gate_up}_exps``)
|
|
17
|
+
of blocks ``i < N`` on the CPU; every always-on tensor of a GPU block stays
|
|
18
|
+
on the GPU.
|
|
19
|
+
* KV / recurrent state lives with its block (offload_kqv default): full
|
|
20
|
+
attention = ``ctx x n_head_kv x (k_len + v_len) x dtype``; sliding-window
|
|
21
|
+
blocks cap the cells at ``window + ubatch``; linear-attention (Gated
|
|
22
|
+
DeltaNet / Mamba) blocks hold a fixed f32 state per sequence, independent
|
|
23
|
+
of ctx.
|
|
24
|
+
|
|
25
|
+
The weights margin applies to the WEIGHTS of each side separately (measured
|
|
26
|
+
per side when a record exists, else the prior); KV, state, the compute
|
|
27
|
+
cushion and an mmproj projector are added unmultiplied. A dense model keeps
|
|
28
|
+
the historical pricing (file bytes x margin on the GPU when fully offloaded),
|
|
29
|
+
so dense admission is byte-for-byte unchanged.
|
|
30
|
+
"""
|
|
31
|
+
from __future__ import annotations
|
|
32
|
+
|
|
33
|
+
from typing import Any, Optional
|
|
34
|
+
|
|
35
|
+
COMPUTE_CUSHION_BYTES = 512 * 2 ** 20 # spill._CTX_COMPUTE_RESERVE_BYTES
|
|
36
|
+
SWA_UBATCH = 512 # llama.cpp default n_ubatch
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def structure_for(path: Optional[str]) -> dict:
|
|
40
|
+
"""``gguf_structure`` of the served file, ``{}`` when unreadable."""
|
|
41
|
+
if not path:
|
|
42
|
+
return {}
|
|
43
|
+
try:
|
|
44
|
+
from hugpy_storage.gguf_inspect import gguf_structure
|
|
45
|
+
return gguf_structure(path) or {}
|
|
46
|
+
except Exception: # noqa: BLE001 — a structure gap degrades to opaque pricing
|
|
47
|
+
return {}
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _layers(struct: dict) -> dict:
|
|
51
|
+
return {int(k): v for k, v in (struct.get("layers") or {}).items()}
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def kv_breakdown(struct: dict, ctx: Optional[int], *, dtype_bytes: float = 2.0,
|
|
55
|
+
n_seq: int = 1, gpu_layers: Optional[set] = None) -> dict:
|
|
56
|
+
"""Context cost by attention kind: ``{kv_bytes, kv_full_bytes, kv_swa_bytes,
|
|
57
|
+
state_bytes, layers: {full, swa, linear}, per_token_bytes}``; with
|
|
58
|
+
``gpu_layers`` also the GPU-side share (``gpu_bytes``)."""
|
|
59
|
+
ctx = max(0, int(ctx or 0))
|
|
60
|
+
win = struct.get("swa_window")
|
|
61
|
+
swa_cells = min(ctx, int(win) + SWA_UBATCH) if win else ctx
|
|
62
|
+
full = swa = state = gpu = 0
|
|
63
|
+
per_tok = 0
|
|
64
|
+
counts = {"full": 0, "swa": 0, "linear": 0}
|
|
65
|
+
for i, L in _layers(struct).items():
|
|
66
|
+
kind = L.get("attn")
|
|
67
|
+
e = int(L.get("kv_elems_per_token") or 0)
|
|
68
|
+
b = 0
|
|
69
|
+
if kind == "full":
|
|
70
|
+
b = int(ctx * e * dtype_bytes)
|
|
71
|
+
full += b
|
|
72
|
+
per_tok += int(e * dtype_bytes)
|
|
73
|
+
elif kind == "swa":
|
|
74
|
+
b = int(swa_cells * e * dtype_bytes)
|
|
75
|
+
swa += b
|
|
76
|
+
elif kind == "linear":
|
|
77
|
+
b = int(L.get("state_bytes") or 0) * max(1, int(n_seq))
|
|
78
|
+
state += b
|
|
79
|
+
if kind in counts:
|
|
80
|
+
counts[kind] += 1
|
|
81
|
+
if gpu_layers is not None and i in gpu_layers:
|
|
82
|
+
gpu += b
|
|
83
|
+
out = {"ctx": ctx, "kv_bytes": full + swa + state, "kv_full_bytes": full,
|
|
84
|
+
"kv_swa_bytes": swa, "state_bytes": state, "layers": counts,
|
|
85
|
+
"per_token_bytes": per_tok, "swa_window": win}
|
|
86
|
+
if gpu_layers is not None:
|
|
87
|
+
out["gpu_bytes"] = gpu
|
|
88
|
+
return out
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def gguf_need(struct: dict, *, ctx: Optional[int], n_cpu_moe: Optional[int] = 0,
|
|
92
|
+
n_gpu_layers: int = -1, gpu_margin: float = 1.15,
|
|
93
|
+
ram_margin: float = 1.15, dtype_bytes: float = 2.0,
|
|
94
|
+
cushion_bytes: int = COMPUTE_CUSHION_BYTES, mmproj_bytes: int = 0,
|
|
95
|
+
n_seq: int = 1) -> dict:
|
|
96
|
+
"""GPU / RAM bytes of a llama-server load at ``ctx`` (see module doc)."""
|
|
97
|
+
layers = _layers(struct)
|
|
98
|
+
n_block = int(struct.get("block_count") or len(layers))
|
|
99
|
+
ngl = int(n_gpu_layers if n_gpu_layers is not None else -1)
|
|
100
|
+
if ngl < 0 or ngl > n_block:
|
|
101
|
+
start, out_on_gpu = 0, True
|
|
102
|
+
else:
|
|
103
|
+
start, out_on_gpu = n_block - ngl, False
|
|
104
|
+
gpu_layers = {i for i in layers if i >= start} if ngl != 0 else set()
|
|
105
|
+
is_moe = bool(struct.get("is_moe"))
|
|
106
|
+
ncm = max(0, int(n_cpu_moe or 0)) if is_moe else 0
|
|
107
|
+
gw = rw = 0
|
|
108
|
+
experts_gpu = experts_cpu = 0
|
|
109
|
+
for i, L in layers.items():
|
|
110
|
+
a, e = int(L.get("always_bytes") or 0), int(L.get("expert_bytes") or 0)
|
|
111
|
+
if i in gpu_layers:
|
|
112
|
+
gw += a
|
|
113
|
+
if i < ncm:
|
|
114
|
+
rw += e
|
|
115
|
+
experts_cpu += e
|
|
116
|
+
else:
|
|
117
|
+
gw += e
|
|
118
|
+
experts_gpu += e
|
|
119
|
+
else:
|
|
120
|
+
rw += a + e
|
|
121
|
+
experts_cpu += e
|
|
122
|
+
embd = int(struct.get("token_embd_bytes") or 0)
|
|
123
|
+
outb = int(struct.get("output_bytes") or 0)
|
|
124
|
+
glob = int(struct.get("global_bytes") or 0)
|
|
125
|
+
rw += embd # input layer: always CPU
|
|
126
|
+
if out_on_gpu:
|
|
127
|
+
gw += outb + glob + (embd if struct.get("output_tied") else 0)
|
|
128
|
+
else:
|
|
129
|
+
rw += outb + glob
|
|
130
|
+
file_bytes = int(struct.get("file_bytes") or 0)
|
|
131
|
+
if not is_moe and out_on_gpu and file_bytes:
|
|
132
|
+
# Dense, fully offloaded: the historical pricing (file x margin on the
|
|
133
|
+
# GPU) — dense admission stays byte-identical.
|
|
134
|
+
gw, rw = file_bytes, 0
|
|
135
|
+
kv = kv_breakdown(struct, ctx, dtype_bytes=dtype_bytes, n_seq=n_seq,
|
|
136
|
+
gpu_layers=gpu_layers)
|
|
137
|
+
kv_gpu = int(kv.get("gpu_bytes") or 0)
|
|
138
|
+
kv_cpu = int(kv["kv_bytes"]) - kv_gpu
|
|
139
|
+
# Compute cushion on the GPU for a MoE split (dense pricing never carried one).
|
|
140
|
+
cushion = int(cushion_bytes) if (gpu_layers and is_moe) else 0
|
|
141
|
+
gpu_total = int(gw * float(gpu_margin)) + kv_gpu + cushion + int(mmproj_bytes or 0)
|
|
142
|
+
ram_total = int(rw * float(ram_margin)) + kv_cpu
|
|
143
|
+
return {"n_cpu_moe": (min(ncm, n_block) if is_moe else None),
|
|
144
|
+
"n_gpu_layers": ngl, "block_count": n_block, "is_moe": is_moe,
|
|
145
|
+
"ctx": kv["ctx"],
|
|
146
|
+
"gpu_bytes": gpu_total, "ram_bytes": ram_total,
|
|
147
|
+
"gpu_weights_bytes": int(gw), "ram_weights_bytes": int(rw),
|
|
148
|
+
"gpu_margin": float(gpu_margin), "ram_margin": float(ram_margin),
|
|
149
|
+
"experts_gpu_bytes": int(experts_gpu), "experts_cpu_bytes": int(experts_cpu),
|
|
150
|
+
"kv_bytes": int(kv["kv_bytes"]), "kv_gpu_bytes": kv_gpu, "kv_cpu_bytes": kv_cpu,
|
|
151
|
+
"kv_full_bytes": kv["kv_full_bytes"], "kv_swa_bytes": kv["kv_swa_bytes"],
|
|
152
|
+
"state_bytes": kv["state_bytes"], "kv_layers": kv["layers"],
|
|
153
|
+
"cushion_bytes": cushion, "mmproj_bytes": int(mmproj_bytes or 0),
|
|
154
|
+
# mmap: the whole file is mapped; pages of GPU-uploaded tensors stay
|
|
155
|
+
# in page cache (RSS) until reclaimed — reclaimable, not a need.
|
|
156
|
+
"mmap_bytes": file_bytes}
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def choose_n_cpu_moe(struct: dict, room_bytes: Optional[int], **kw) -> dict:
|
|
160
|
+
"""The SMALLEST ``n_cpu_moe`` whose GPU side fits ``room_bytes`` whole
|
|
161
|
+
(GPU bytes fall monotonically as N grows). ``fits`` False -> every expert
|
|
162
|
+
on the CPU and the backbone still does not fit (N = block_count)."""
|
|
163
|
+
n_block = int(struct.get("block_count") or 0)
|
|
164
|
+
if room_bytes is None:
|
|
165
|
+
need = gguf_need(struct, n_cpu_moe=n_block, **kw)
|
|
166
|
+
return dict(need, fits=None, choice="unmeasurable card — all experts on CPU")
|
|
167
|
+
for n in range(0, n_block + 1):
|
|
168
|
+
need = gguf_need(struct, n_cpu_moe=n, **kw)
|
|
169
|
+
if need["gpu_bytes"] <= int(room_bytes):
|
|
170
|
+
return dict(need, fits=True, room_bytes=int(room_bytes),
|
|
171
|
+
choice=f"smallest n_cpu_moe fitting {int(room_bytes)} B")
|
|
172
|
+
need = gguf_need(struct, n_cpu_moe=n_block, **kw)
|
|
173
|
+
return dict(need, fits=False, room_bytes=int(room_bytes),
|
|
174
|
+
choice="backbone does not fit even with every expert on CPU")
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def whole_seat_ctx(need_at, room: int, upper: int, floor: int = 4096) -> "tuple[int, bool]":
|
|
178
|
+
"""Largest ctx (1024 multiple in [floor, upper]) with ``need_at(ctx) <= room``."""
|
|
179
|
+
upper = max(int(floor), int(upper))
|
|
180
|
+
top = upper if upper < 1024 else (upper // 1024) * 1024
|
|
181
|
+
if need_at(top) <= room:
|
|
182
|
+
return top, True
|
|
183
|
+
lo, hi, best = -(-int(floor) // 1024), top // 1024, None
|
|
184
|
+
while lo <= hi:
|
|
185
|
+
mid = (lo + hi) // 2
|
|
186
|
+
if need_at(mid * 1024) <= room:
|
|
187
|
+
best, lo = mid * 1024, mid + 1
|
|
188
|
+
else:
|
|
189
|
+
hi = mid - 1
|
|
190
|
+
if best is None:
|
|
191
|
+
return int(floor), need_at(int(floor)) <= room
|
|
192
|
+
return max(int(floor), best), True
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
def verdict_text(need: dict) -> str:
|
|
196
|
+
""""MoE: 12/48 layers' experts on CPU; GPU 17.2 GiB, RAM 21.0 GiB" (+ KV)."""
|
|
197
|
+
g = 2 ** 30
|
|
198
|
+
if not need.get("is_moe"):
|
|
199
|
+
return (f"GPU {need['gpu_bytes'] / g:.1f} GiB, RAM {need['ram_bytes'] / g:.1f} GiB "
|
|
200
|
+
f"(KV {need['kv_bytes'] / g:.2f} GiB @ {need['ctx']})")
|
|
201
|
+
return (f"MoE: {need['n_cpu_moe']}/{need['block_count']} layers' experts on CPU; "
|
|
202
|
+
f"GPU {need['gpu_bytes'] / g:.1f} GiB, RAM {need['ram_bytes'] / g:.1f} GiB "
|
|
203
|
+
f"(KV {need['kv_bytes'] / g:.2f} GiB @ {need['ctx']} ctx)")
|