Repository navigation
[None][feat] Classify startup failures, surface their root cause and report them in telemetry - #20853
Conversation
56a771c to
89dc95d
Compare
89dc95d to
43c4563
Compare
742ccc3 to
e5f199a
Compare
|
Could you upload schema 0.8 to SMS, enable it in staging, and test representative events? Please verify the new fields in Kibana Discover using the |
e8cd302 to
60c2287
Compare
|
/bot run |
|
PR_Github #82924 [ run ] completed with state
|
472ae0b to
201b460
Compare
|
/bot run --disable-fail-fast |
|
PR_Github #83064 [ run ] triggered by Bot. Commit: |
|
PR_Github #83064 [ run ] completed with state
|
d77bcd0 to
710b431
Compare
|
/bot run --disable-fail-fast |
|
PR_Github #83404 [ run ] triggered by Bot. Commit: |
|
PR_Github #83404 [ run ] completed with state
|
…report them in telemetry Startup failures inside executor workers reach users as "Executor worker returned error" or "MPI worker exited unexpectedly", with the real cause only in earlier log lines. Usage telemetry cannot tell why a start failed either, or whether failures come from one deployment stuck in a restart loop: the exit report carries no cause, and every process gets a new random session ID. - Classify startup failures into coarse categories (config_input, resource, unsupported, model_load, runtime_init, other) and log one ERROR line with the root cause and an actionable hint at the LLM boundary, in the proxy before an abort that may end the process, and at the CLI boundary until an LLM has initialized in the process. - Let TensorRT-LLM raise sites declare the category with one exception class per category that derives only from its builtin, so callers can still catch it as ValueError or RuntimeError. About 50 startup raise sites are converted, a few of them to a different builtin where nothing catches the old one, and QuantAlgo raises UnsupportedError for a name it does not know. Errors that TensorRT-LLM does not raise pass through unchanged and are classified by exception type, error code, a fixed list of message phrases and the raising module; e.g. a --config file that does not exist is invalid input in trtllm-serve, trtllm-bench and trtllm-eval. - Carry the worker's classification, rank and initialization stage to the proxy, including from non-leader ranks, so the final error names the root cause; a "[<category>]" marker carries it in the worker error that is forwarded as text. - Fail fast on certain preconditions before weights are loaded: no visible GPU or fewer GPUs than an LLM-spawned MPI pool needs on this node, a GPU below the documented minimum for the quantization format, and an unregistered model architecture. - Report the category of a startup failure on the usage telemetry exit report, and a bucketed count of consecutive failed starts of the same launch on the exit and initial reports. The count lives in a small local file that only the reporting process reads or writes, never when telemetry is disabled or after a late opt-out. Both reports wait at most 100 ms for that I/O, so a stalled file system cannot hold up shutdown or the initial report. VisualGen sessions report neither field. The event schema goes to 0.8, and the usage notice, the README and the schema reference describe the fields and the file. Signed-off-by: Zhenhua Wang <zhenhuaw@nvidia.com>
…lookup Address review feedback on the startup checks: - FP8 block scales and FP8 per-channel per-token now require SM89 instead of SM90. Their GEMMs dispatch on Ada, so dense checkpoints such as Qwen3-8B-FP8 on L40S were rejected before loading. - Which module provides an architecture is now resolved in one place, _model_arch_provider() in modeling_utils.py: external declarations first, then the static index. _ensure_model_registered() imports what it returns, and the new is_model_arch_known() checks the registry and then that same function without importing. The startup architecture check and trtllm-serve's has_registered_llm_architecture() both call is_model_arch_known(), so a new provider source only has to be added to _model_arch_provider() and every caller follows. Signed-off-by: Zhenhua Wang <zhenhuaw@nvidia.com>
710b431 to
6630b10
Compare
|
/bot run --disable-fail-fast |
|
PR_Github #83459 [ run ] triggered by Bot. Commit: |
|
PR_Github #83459 [ run ] completed with state
|
|
/bot run --disable-fail-fast |
|
PR_Github #83604 [ run ] triggered by Bot. Commit: |
|
PR_Github #83604 [ run ] completed with state
|
|
/bot run --disable-fail-fast |
|
PR_Github #83651 [ run ] triggered by Bot. Commit: |
|
PR_Github #83651 [ run ] completed with state
|
|
/bot run --disable-fail-fast |
|
PR_Github #83679 [ run ] triggered by Bot. Commit: |
|
PR_Github #83679 [ run ] completed with state |
Description
When
LLM()ortrtllm-servefails to start, the cause usually lives in an executor worker and users only seeRuntimeError: Executor worker returned error, orMPI worker exited unexpectedlywhen a non-leader rank fails. This PR makes startup failures self-explanatory and reports their category and restart loops in usage telemetry. Nothing changes on the success path and no defaults change._StartupTimer/_WarmupTimerphases. Non-leader ranks now raise a string-only error, so the proxy sees it through the MPI future, or through thetrtllm-llmapi-launchforwarder. Example:Executor worker returned error on rank 1 during executor_creation/model: [resource] RuntimeError: CUDA Runtime API error: <cudaError_t.cudaErrorMemoryAllocation: 2>.config_input,resource,unsupported,model_load,runtime_init(bringing up the runtime failed: worker death during init, distributed setup, kernel compilation) andother(not attributable; internal-looking errors such as anAttributeErrorraised inside TRT-LLM or an unknown CUDA error code land here with a "please report it" hint). The categories, their exception classes and the standard hints live in the private, stdlib-onlytensorrt_llm/_errors.py, which is not tied to startup and is not exported. The hint key doubles as the error's reason in code; nothing is reported.ConfigInputErrorandUnsupportedErrorareValueErrors;ResourceError,ModelLoadErrorandRuntimeInitErrorareRuntimeErrors, so callers can always catch them withexcept ValueError/except RuntimeError. They share code through a private mixin that is not an exception type, so there is no new base to catch. The category travels with the type; messages carry no marker. Any other exception class can declare a category with acategoryclass attribute. Raise sites use the class even where that changes the builtin of an existing raise. About 50 startup raise sites are converted, among them: worker death detected by the proxy, a rank that sees no GPU (was aZeroDivisionError), conflicting checkpoint quantization configs, missing weight files, unknown architectures, MoE backend resolution, KV cache budget, SM gates, model-loader config validators, YAML files whose root is not a mapping, and the trtllm-bench memory estimate.QuantAlgoraisesUnsupportedErrorfor a name it does not know, e.g. a checkpoint quantized with a newer format; it is still aValueErrorwith the same message, and pydantic still reports an invalid config value as a validation error.FileNotFoundErrorraised in CLI code (trtllm-serve, includingdisaggregated -c,trtllm-bench,trtllm-eval) isconfig_inputwith the config hint, while missing weights staymodel_load. The only text marker is in the worker error thattrtllm-llmapi-launchforwards as text, which carries the worker's category as[<category>] ....tensorrt_llm/_startup_errors.pyclassifies a failure (declared category first, then exception type, errno and CUDA error code, then a fixed list of message phrases and the raising module) and logs one ERROR line with an actionable hint at theLLM()boundary, in the proxy before an abort that can end the process (MPI_Abort), and at the CLI boundary. Once anLLMhas initialized in the process, the CLI boundary no longer classifies: a later crash keeps just its traceback, while anotherLLM()that fails to start is still classified at its own boundary.tensorrt_llm/llmapi/startup_checks.pyruns before workers load weights and fails on:CUDA_VISIBLE_DEVICES, the NVML GPU count and driver CUDA version, and the likely causes (multi-GPULLM(),trtllm-serve, and every worker rank);is_model_arch_knowninmodeling_utils.py, whichtrtllm-serve's diffusion routing now uses as well).ValueErrorinstead of inside the worker (test_modeling_out_of_treeupdated).RuntimeError→ValueError: too few MPI ranks for the executor (ConfigInputError), and the SM gates of DeepSeek-V4, MiniMax-M3 MSA, NVFP4 cold-page quantization, TriAttention and the DeepGEMM path of the Cutlass MoE (UnsupportedError).ValueError→RuntimeError: a pre-quantized checkpoint withoutquant_algo(ModelLoadError), a KV cache budget exhausted by the DeepSeek-V4 indexer workspace or by the draft's fixed cost, andtrtllm-servewithout a visible GPU (ResourceError).TypeError→ValueError: a trtllm-benchextra_llm_api_optionsfile whose root is not a mapping (ConfigInputError).get_model_architectureraisesConfigInputError(Gemma4 needs a newer transformers) andUnsupportedError(unknown architecture) instead ofRuntimeError; its two fallbacks, ininputs/registry.pyandmodeling_dflash.py, catch these two classes as well, so they fall back exactly as before.Executor worker returned error ...line carries the worker's category before the root cause.gpus_per_node, which defaults to the local GPU count.errorCategoryon the exit report: the category above for a session that ended during startup with an exception (or an LLM API exit after a failedLLM()), recorded at theLLM()failure handler and at the CLI boundary;""otherwise, and for child failures seen by a supervisor.priorStartupFailuresandstartupStateStatuson the exit and initial reports: how many times in a row this launch had failed to start, bucketed (0, 1, 2, 3, 10, 30, 100, 300, 1000), from a small local file per launch (~/.cache/tensorrt_llm/usage/, orTRTLLM_USAGE_STATE_DIR). The file name hashes inputs that stay the same when a launch restarts: the TRT-LLM version, command line, GPU assignment, launcher rank and process role (not the Slurm job ID or the host name, which can change with every submission), so copies of one launch on hosts that share a home directory share one count; none of these is sent. A failed start adds one, an initialized LLM or serving resets it, signals leave it. Only the reporting process touches the file, at most once, never when telemetry is disabled; an opt-out that arrives while the process runs (e.g. a laterTelemetryConfig(disabled=True)) stops any file access that has not started. I/O errors only set the status tounavailable.priorStartupFailures=0andstartupStateStatus="unavailable", so a stalled file system holds up neither shutdown nor the initial report and heartbeats; the exit report keeps itserrorCategory, and a write that finishes later is still atomic._errors.pynotes that the category names are a data contract.Test Coverage
tests/unittest/others/test_startup_errors.py(categories, reasons, the category classes, a missing file opened by CLI code versus elsewhere, an unknownQuantAlgo, the CLI boundary skipping failures after anLLMinitialized),tests/unittest/llmapi/test_startup_checks.pyandtests/unittest/inputs/test_input_processor_fallback.py(an unregistered or Gemma4-without-transformers architecture still gets theDefaultInputProcessor), all inl0_cpu.tests/unittest/executor/test_proxy_fast_death.pycovers the worker classification carried to the proxy, non-leader errors, and the hint logged before the abort.test_modeling_out_of_tree.pyfor the new failure point, and four KV cache tests for the new builtin of their raise site (test_indexer_workspace_reserve,test_kv_cache_budget_split,test_quantization_for_cold_page,test_kv_cache_compression_manager).tests/unittest/usage/test_startup_state.py(buckets, launch key, file rules, corrupt and unusable state, pruning) and new cases intest_reporter.py(exit rules, restart loop, success reset, partial report, VisualGen, opt-outs touching no file, also a late opt-out in the reporter and on the exit path, a blocked state file and a held state lock within the exit bound, and the initial report not waiting for them),test_cli_telemetry.py(CLI usage errors,SystemExitcauses, precedence of theLLM()category, a missing--configfile in each CLI, an explicit VisualGentrtllm-servethat fails an option check inserve()or in Click's own parsing reporting no startup fields),test_schema.py(enums match the classifier and the buckets),test_llm_telemetry.pyandtest_e2e_capture.py.TestStagingEndpoint(--run-stagingonly) adds an exit report with everyerrorCategoryandstartupStateStatusvalue and a partial initial report.schema.build_gxt_payload(exit reports with everyerrorCategoryandstartupStateStatusvalue andpriorStartupFailuresup to 100, a heartbeat, a partial initial report sent with its exit report) were accepted by gfestage, and each one is inPDX-UAT:stage-events-trtllmwith the values sent. The stage gateway does not validate against 0.8 yet; the rollout status is in the PR conversation.a545268cf3(one import conflict ininputs/registry.pywith [TRTLLM-16725][refactor] BREAKING: Use a string enum for checkpoint formats and simplify checkpoint I/O helpers #20703) and building the C++ from source:test_startup_checks43 passed (FP8 block scales and per-channel per-token accepted on SM89 and rejected on SM80, an externally declared architecture accepted); CPU-stage emulation oftest_startup_checks,test_lazy_model_zoo,test_startup_errorsandusage: 718 passed;test_llm_args,test_lazy_model_zoo,test_serve_diffusion_routingandtest_startup_checks: 648 passed; GPU stage (test_modeling_out_of_tree,test_llm_telemetry,test_proxy_fast_death): 79 passed.a7e88dee4a(since rebased ontoaea80a0ec8without conflicts, which picks up main's fix for the disaggregated context-first BF16 tests and its waiver of the MBART tokenizer test), with the C++ built from source (the newest precompiled wheel is 7 commits behind, with C++ changes since):test_startup_checks(41 passed) andtest_cli_telemetry(72 passed) cover the architecture check (model_kwargs["architectures"]when set, no check with a customcheckpoint_loader) and the VisualGen flag detection before Click parses the options. The unregistered-architecture launch gives the same category, hint and final line as before, and the same checkpoint withmodel_kwargs={"architectures": ["LlamaForCausalLM"]}starts.test_vision_gpu_normalize_reaches_input_processordrives_build_modelwith a stubargs, so it now also stubs the two startup checks_build_modelruns;test_llm_argsin the CPU-stage emulation: 463 passed.-m cpu_only, no visible GPU), includingtests/unittest/inputs,test_hf_quant_config,quantization/test_modeandapi_stability: 1768 passed, 1 skipped, among them the classification of main's "not included in this TensorRT-LLM build" architecture error asunsupported; the new timing-dependent telemetry tests passed 30 of 30 repeats. GPU stage (test_proxy_fast_death,test_llm_telemetry,test_modeling_out_of_tree): 79 passed.test_llm_telemetryandtest_disagg_telemetry_launcherwith GPUs: 52 passed. Telemetry capture tests (test_e2e_capture,test_transport): 15 passed, 5 staging-only skipped; the staging cases pass with each POST validated against the JSON schema locally instead of sent.test_llm_args,test_postprocessor_hook,test_indexer_workspace_reserve,test_kv_cache_compression_manager): 578 passed;tests/unittest/inputswith theget_model_architecturecallers (test_lilicorr, the Gemma4 wrapper test): 383 passed, 1 skipped; config loaders (test_disagg_utils,test_disagg_telemetry_launcher,test_image_generation_eval_cli): 69 passed; main'stest_cuda_architecture_check: 5 passed; main'svalidate_fp4_mla_configtests: 6 passed (its FP4 MLA checks raiseUnsupportedError, andConfigInputErrorforuse_kv_cache_manager_v2=False); main's modeling_v2 routing tests, which resolve throughAutoModelForCausalLM: 336 passed, 70 skipped.trtllm-serve/LLM()launches: HF 401 and offline, config typos, a missing--configfile fortrtllm-serveand fortrtllm-serve disaggregated -c, no visible GPU, TP > GPUs, port in use (caught by main's multi-frontend port reservation), rank-1 OOM through the MPI pool and throughtrtllm-llmapi-launch, unregistered architecture, truncated weights, killed worker, and a failing custom module. All give the expected category, hint and final line, unchanged from before the rebase.0andnew; the killed-worker launch sends no report because the process ends inMPI_Abort, as before this PR. Atrtllm-serveloop on a busy port reports 0, 1, 2, 3, 3; once the port is free, the initial report carries 3 and the count resets. An LLM API loop on truncated weights behaves the same way. Two instances on different GPUs keep separate counts, and the same launch on two simulated hosts sharing a state directory continues one count. Opt-out creates no file, an unusable state directory reportsunavailable, and VisualGen reports""and creates no file, also when--enable_visual_genfails an early option check, inserve()or in Click's parsing (--port invalid), twice in a row (the same launch without the flag counts 0, then 1). A state file that blocks on open (a FIFO) still lets failing and successful starts send their reports withunavailable.trtllm-servestarts 8 frontends by default ([TRTLLM-14510][feat] BREAKING: serve: default num_serve_frontends to 8 with single-frontend fallback #19379), and each attached frontend reports as its own session under its own launch key (TLLM_EXECUTOR_FRONTEND_ID). A successful default start therefore sends 8 initial and exit reports and keeps 8 state files: the launcher's reports carry the count and the 7 frontends' report 0. Failed starts end before the frontends spawn and are counted once; with--num_serve_frontends 1the values equal those before the rebase. The state file costs about 3 to 9 ms on an NFS home depending on the node, on either path (0.7 ms on local disk for the exit path); when the I/O blocks,report_exitreturns after 101 ms and the reporter moves on after 100 ms.PR Checklist
Please review the following before submitting your PR:
PR description clearly explains what and why. If using CodeRabbit's summary, please make sure it makes sense.
PR Follows TRT-LLM CODING GUIDELINES to the best of your knowledge.
Test cases are provided for new code paths (see test instructions)
If PR introduces API changes, an appropriate PR label is added - either
api-compatibleorapi-breaking. Forapi-breaking, includeBREAKINGin the PR title.Any new dependencies have been scanned for license and vulnerabilities
CODEOWNERS updated if ownership changes
Documentation updated as needed
Update tava architecture diagram if there is a significant design change in PR.
The reviewers assigned automatically/manually are appropriate for the PR.
Please check this after reviewing the above items as appropriate for this PR.
GitHub Bot Help
To see a list of available CI bot commands, please comment
/bot help.Dev Engineer Review
QA Engineer Review
tests/integration/test_lists/test-db/l0_cpu.ymladdsunittest/llmapi/test_startup_checks.py. Other changed tests with list references appear inl0_a10.yml,l0_cpu.yml, orl0_h100.yml. No changed test was found in the manual-QA lists. The remaining changed tests have no literal list reference in the inspected lists.Per-File QA Perspective
Documentation and source files
README.md— Verify that the documented categories, local failure counts, storage paths, and opt-out behavior match the implementation.tensorrt_llm/_errors.py— Verify exception inheritance and category, reason, and hint defaults. Check compatibility for callers that catch prior exception types.tensorrt_llm/_startup.py— Verify failed timers and nested phases record the intended stage, including forBaseException.tensorrt_llm/_startup_errors.py— Verify exception-chain classification, worker metadata propagation, summary limits, and preservation of original errors if classification or logging fails.tensorrt_llm/_torch/attention/backends/sparse/deepseek_v4/module.py— Verify below-SM90 devices raiseUnsupportedErrorand callers handle it.tensorrt_llm/_torch/attention/backends/sparse/minimax_m3/msa_availability.py— Verify unsupported GPU architectures raiseUnsupportedError.tensorrt_llm/_torch/custom_ops/torch_custom_ops.py— Verify only the DeepGEMM-on-non-Hopper failure is recategorized and its cause remains chained.tensorrt_llm/_torch/model_config.py— Verify conflicting KV-cache quantization settings raiseModelLoadError.tensorrt_llm/_torch/models/checkpoints/hf/weight_loader.py— Verify missing usable checkpoint files raiseModelLoadError.tensorrt_llm/_torch/models/checkpoints/mistral/config_loader.py— Verify unknown quantization formats raiseUnsupportedError.tensorrt_llm/_torch/models/modeling_auto.py— Verify unknown model architectures raiseUnsupportedErrorwith the intended messages.tensorrt_llm/_torch/models/modeling_dflash.py— Verify typed exceptions trigger the intended architecture fallback.tensorrt_llm/_torch/models/modeling_utils.py— Verify error types for missing architectures, unsupported Gemma4 architectures, and other unknown architectures.tensorrt_llm/_torch/moe/fused_moe/moe_resolution.py— Verify unavailable or disallowed degraded MoE implementations raiseUnsupportedError.tensorrt_llm/_torch/pyexecutor/_util.py— Verify budget failures raiseResourceErrorand unsupported-device paths raiseUnsupportedError.tensorrt_llm/_torch/pyexecutor/model_loader.py— Verify affected configuration failures use the intended exception classes.tensorrt_llm/_torch/pyexecutor/warmup_timer.py— Verify warmup failures record their phase before re-raising, including non-Exceptionfailures.tensorrt_llm/bench/benchmark/utils/general.py— Verify non-mapping YAML values raiseConfigInputError.tensorrt_llm/bench/benchmark/visual_gen.py— Verify VisualGen is marked before telemetry records startup state.tensorrt_llm/bench/tuning/heuristics.py— Verify insufficient engine or KV-cache capacity raisesResourceErrorwith actionable guidance.tensorrt_llm/commands/_telemetry.py— Verify startup categories are reported only before successful LLM initialization and terminal outcomes retain prior behavior.tensorrt_llm/commands/eval.py— Verify invalid YAML roots raiseConfigInputErrorandNoneremains accepted.tensorrt_llm/commands/serve.py— Verify GPU detection, VisualGen selection, and YAML and gRPC validation paths.tensorrt_llm/executor/base_worker.py— Verify no-visible-GPU detection runs before device-ID calculation and valid device selection remains unchanged.tensorrt_llm/executor/executor.py— Verify insufficient MPI ranks produce parallel-size guidance and valid launches remain unchanged.tensorrt_llm/executor/postprocessor_hook.py— Verify hook import, construction, and callability failures raiseConfigInputError.tensorrt_llm/executor/proxy.py— Verify classified worker failures reach ordered teardown with rank and root-cause details. Check that unclassified worker deaths retain their existing path.tensorrt_llm/executor/worker.py— Verify worker classification and leader status reporting preserve errors across ranks and process boundaries.tensorrt_llm/inputs/registry.py— Verify typed architecture errors still selectDefaultInputProcessorwhere intended.tensorrt_llm/llmapi/llm.py— Verify initialization-failure reporting, success marking, GPU preflight, and architecture and quantization checks.tensorrt_llm/llmapi/llm_args.py— Verify invalid YAML roots raiseConfigInputError.tensorrt_llm/llmapi/llm_utils.py— Verify quantization conflicts and unsupported formats use the intended exception types and hints.tensorrt_llm/llmapi/startup_checks.py— Verify GPU-count requirements for local and distributed launches, architecture registration, and quantization checks, including skip conditions.tensorrt_llm/models/quant_config_utils.py— Verify conflicting FP8 KV-cache configuration raisesConfigInputError.tensorrt_llm/quantization/mode.py— Verify unknown enum values raiseUnsupportedErrorwith supported-format guidance.tensorrt_llm/usage/__init__.py— Verifyrecord_startup_error_categoryis exposed through the package API.tensorrt_llm/usage/schema.py— Verify schema 0.8 models enforce the new field types and defaults.tensorrt_llm/usage/schemas/README.md— Verify documented fields, state lifecycle, environment variable, and VisualGen behavior match the implementation.tensorrt_llm/usage/schemas/trtllm_usage_event_schema.json— Verify the JSON schema requires and constrains the new fields. Confirm downstream consumers accept schema 0.8.tensorrt_llm/usage/startup_state.py— Verify launch-key stability, path precedence, expiry, file permissions, atomic writes, pruning, and storage-error fallbacks.tensorrt_llm/usage/usage_lib.py— Verify category precedence, startup-state actions, timeouts, opt-out behavior, and VisualGen exclusions in initial and terminal reports.Test and test-list files
tests/integration/test_lists/test-db/l0_cpu.yml— Addsunittest/llmapi/test_startup_checks.py. Verify that the entry resolves to the intended test.tests/unittest/_torch/kv_cache_compression/test_quantization_for_cold_page.py— Updates the expected exception for unsupported cold-page hardware. No inspected CI or manual-QA list reference was found.tests/unittest/_torch/modeling/test_modeling_out_of_tree.py— Changes the unknown-architecture expectation and removes cause inspection. No inspected list reference was found.tests/unittest/_torch/pyexecutor/kv_cache/test_kv_cache_budget_split.py— Updates expected budget-failure exception types. No inspected list reference was found.tests/unittest/_torch/pyexecutor/kv_cache/test_kv_cache_compression_manager.py— Updates the TriAttention compatibility exception expectation. Referenced intest-db/l0_a10.yml.tests/unittest/_torch/pyexecutor/test_indexer_workspace_reserve.py— Updates the expected workspace-budget exception type. No inspected list reference was found.tests/unittest/executor/test_proxy_fast_death.py— Adds coverage for classified non-leader failures and propagation of rank, stage, root cause, and classification. Referenced intest-db/l0_a10.yml.tests/unittest/inputs/test_input_processor_fallback.py— Adds fallback coverage for unregistered architectures and typed errors. No inspected list reference was found.tests/unittest/llmapi/test_llm_args.py— Updates startup-check mocks while testingvision_gpu_normalizeforwarding. Referenced intest-db/l0_cpu.ymlandtest-db/l0_a10.yml.tests/unittest/llmapi/test_llm_telemetry.py— Tests initialization-failure categories and category clearing after successful initialization. Referenced intest-db/l0_cpu.yml,test-db/l0_a10.yml, andtest-db/l0_h100.yml.tests/unittest/llmapi/test_startup_checks.py— Adds CPU-only coverage for GPU visibility and count, quantization capability, and architecture registration. Referenced intest-db/l0_cpu.yml.tests/unittest/others/test_startup_errors.py— Adds classification, exception-chain, metadata, worker-wrapping, logging, and CLI-boundary coverage. No inspected list reference was found.tests/unittest/usage/conftest.py— Adds per-test startup-state directory isolation. Verify that the fixture does not interfere with tests that set an explicit directory. No inspected list reference was found.tests/unittest/usage/test_cli_telemetry.py— Updates schema expectations and adds CLI category, cause, exit, and restart-count coverage. No inspected list reference was found.tests/unittest/usage/test_e2e_capture.py— Updates end-to-end payload expectations for schema 0.8 and startup-state fields. Referenced intest-db/l0_a10.ymlandtest-db/l0_h100.yml.tests/unittest/usage/test_reporter.py— Adds coverage for failure-count actions, category reporting, VisualGen and opt-out exclusions, and bounded state access. No inspected list reference was found.tests/unittest/usage/test_schema.py— Adds model and JSON-schema consistency checks for category, count, and status values. No inspected list reference was found.tests/unittest/usage/test_startup_state.py— Adds coverage for bucketing, launch keys, storage selection, state actions, expiry, permissions, failures, and pruning. No inspected list reference was found.tests/unittest/usage/test_transport.py— Adds opt-in staging submissions for startup exit and partial initial reports. Referenced intest-db/l0_a10.ymlandtest-db/l0_h100.yml; source material does not establish current-head staging or gateway results.