Skip to content

Hide docker and make conversion rootles - #262

Open
kozlov721 wants to merge 7 commits into
mainfrom
feat/no-root
Open

Hide docker and make conversion rootles#262
kozlov721 wants to merge 7 commits into
mainfrom
feat/no-root

Conversation

@kozlov721

@kozlov721 kozlov721 commented Jul 25, 2026

Copy link
Copy Markdown
Collaborator

Purpose

Makes it much easier to use modelconverter without managing shared directory with containers and dealing with root being the owner of converted files.

Specification

  • Movement of files between the user and the container is now managed automatically in the background.
    • Removed shared_with_container/
    • User can directly call modelconverter convert rvc4 --path ~/Downloads/resnet18.onnx.tar.xz
    • Results will be saved under output/ in CWD
    • Converted results are owned by the user who invoked the conversion
  • Files internally stored in a global cache
    • Added modelconverter cache commands to inspect and purge it

Dependencies & Potential Impact

None / not applicable

Deployment Plan

None / not applicable

Testing & Validation

None / not applicable

AI Usage

Assisted-by: AGENT_NAME:MODEL_VERSION [TOOL1] [TOOL2]

Submitted code was reviewed by a human: YES/NO

The author is taking the responsibility for the contribution: YES/NO

Summary by CodeRabbit

  • New Features

    • Added centralized default and example configuration files, including new single- and multi-stage presets.
    • Introduced CLI cache commands for inspecting and cleaning the local modelconverter cache.
    • Added automatic staging and content-hash deduplication for local CLI inputs.
    • Improved path resolution so config-referenced resources resolve relative to the config location.
  • Bug Fixes

    • Improved Docker output ownership handling and consistent cache/output directory mappings for better local access.
    • Adjusted inference output and analysis paths to match the new output layout.
  • Documentation

    • Updated README with the new cache + local staging workflow and updated directory guidance.
  • Tests

    • Refreshed test output/inference directory wiring and related CLI expectations.

@kozlov721
kozlov721 requested a review from a team as a code owner July 25, 2026 06:12
@kozlov721
kozlov721 requested review from conorsim, klemen1999 and tersekmatija and removed request for a team July 25, 2026 06:12
@coderabbitai

coderabbitai Bot commented Jul 25, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@kozlov721, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 52 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 108b46dd-19f1-42fe-b765-74d74695b84a

📥 Commits

Reviewing files that changed from the base of the PR and between e9ce9a0 and 18de2f1.

📒 Files selected for processing (12)
  • README.md
  • docker-compose.yaml
  • docker/hailo/entrypoint.sh
  • docker/rvc2/entrypoint.sh
  • docker/rvc4/entrypoint.sh
  • modelconverter/__main__.py
  • modelconverter/utils/constants.py
  • modelconverter/utils/docker_utils.py
  • modelconverter/utils/input_staging.py
  • tests/test_utils/test_cache_cli.py
  • tests/test_utils/test_docker_entrypoint.py
  • tests/test_utils/test_input_staging.py
📝 Walkthrough

Walkthrough

The PR replaces the shared project directory workflow with a hidden cache and local output/ directory, stages Docker inputs by content hash, resolves config resources relative to their files, adds cache commands, and restores host ownership after container execution.

Changes

Cache-backed Docker conversion

Layer / File(s) Summary
Configuration and path resolution
modelconverter/utils/constants.py, modelconverter/utils/filesystem_utils.py, modelconverter/cli/utils.py, modelconverter/utils/calibration_data.py, configs/*
Adds Docker-aware cache/output paths, config-relative resource resolution, default configuration, and example model configurations.
Input staging and Docker orchestration
modelconverter/utils/input_staging.py, modelconverter/utils/docker_utils.py, docker-compose.yaml, docker/*/entrypoint.sh, modelconverter/__main__.py
Stages local CLI inputs into hashed cache locations, mounts cache and output directories, conditionally passes host identity, and reapplies ownership after commands finish.
Outputs, cache commands, and validation
modelconverter/__main__.py, modelconverter/packages/rvc4/inferer.py, tests/*, README.md, .dockerignore, .gitignore
Moves inference and analysis artifacts to output/, adds cache inspection and cleanup commands, uses a separate RVC4 scratch directory, updates tests and ignores, and documents the revised workflow.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant CLI
  participant InputCache
  participant DockerCompose
  participant ModelConverter
  participant OutputDirectory
  CLI->>InputCache: Stage local inputs by content hash
  CLI->>DockerCompose: Start with rewritten cache paths
  DockerCompose->>ModelConverter: Run conversion or inference
  ModelConverter->>OutputDirectory: Write artifacts and logs
  DockerCompose-->>CLI: Restore output ownership and exit status
Loading

Possibly related PRs

Suggested reviewers: klemen1999, tersekmatija, conorsim

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 48.78% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title captures the main shift away from container-exposed workflows toward a hidden Docker/cache-based conversion flow.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/no-root

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
modelconverter/cli/utils.py (1)

90-108: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Reset _input_base at the start of get_configs.

set_input_base sets a ContextVar, but this function never calls set_input_base(None). In the CLI entrypoints, convert returns while infer continues in the same process; if the first config file leaves a non-None base, later resolution in infer can use that stale custom directory instead of the default cache/cwd base.

🛡️ Proposed fix: reset the base unconditionally at the top of the function
     opts = opts or []
+    # Reset to the default resolution base so state left over from a
+    # previous call in this process does not leak into this one.
+    set_input_base(None)
     if isinstance(opts, list):
         if len(opts) % 2 != 0:
             raise ValueError(
                 "Invalid number of overrides. See --help for more information."
             )
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@modelconverter/cli/utils.py` around lines 90 - 108, Reset the input base
unconditionally at the start of get_configs by calling set_input_base(None)
before processing opts or path. Preserve the existing
set_input_base(path_.parent) behavior for config-file-relative resolution and
all subsequent archive/config handling.
🧹 Nitpick comments (6)
modelconverter/utils/docker_utils.py (1)

118-119: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Container mount path duplicated across files.

/app/shared_with_container here duplicates the same literal in constants.py, input_staging.py, and docker-compose.yaml. See consolidated comment.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@modelconverter/utils/docker_utils.py` around lines 118 - 119, Replace the
hardcoded /app/shared_with_container mount path in the Docker volume
configuration with the shared constant defined in constants.py, updating the
relevant docker utility symbol while preserving the existing host-path mapping.
modelconverter/utils/constants.py (1)

26-31: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Container mount path duplicated across files.

"/app/shared_with_container" is hardcoded here and re-hardcoded in input_staging.py, docker_utils.py, and docker-compose.yaml. If the mount point ever changes, all four sites must be updated in lockstep. See consolidated comment.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@modelconverter/utils/constants.py` around lines 26 - 31, Centralize the
container mount path used by SHARED_DIR in a single shared constant, then update
input_staging.py, docker_utils.py, and docker-compose.yaml to reference that
same configuration value instead of hardcoding "/app/shared_with_container".
Preserve the existing Docker and non-Docker directory behavior.
modelconverter/utils/input_staging.py (2)

25-25: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Hardcoded container mount path, duplicated across files.

_CONTAINER_SHARED_DIR = Path("/app/shared_with_container") duplicates the same literal in constants.py, docker_utils.py, and docker-compose.yaml. See consolidated comment.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@modelconverter/utils/input_staging.py` at line 25, Replace the hardcoded path
in _CONTAINER_SHARED_DIR with the shared constant defined in constants.py, and
update the related references in docker_utils.py and docker-compose.yaml to use
the same centralized value where applicable. Preserve the existing
/app/shared_with_container behavior while eliminating duplicated literals.

141-146: 🚀 Performance & Scalability | 🔵 Trivial | 🏗️ Heavy lift

Staging a config's whole parent directory can sweep up unrelated files.

Because config-relative references resolve against the copied directory, _stage_value copies src.parent in full for any .yaml/.yml config. If the config lives directly in a broad directory (home dir, Downloads/, a shared project root), the entire tree — including files unrelated to the conversion — gets copied into the persistent cache. The _LARGE_DIR_BYTES warning only fires after the copy decision and doesn't scope what gets copied.

Consider: only copying files actually referenced by the config (would require a lightweight scan of local-file fields), or requiring configs to live in a dedicated directory before auto-staging, or prompting before staging directories above a size/depth threshold.

Also applies to: 186-201

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@modelconverter/utils/input_staging.py` around lines 141 - 146, Update
_stage_value’s config handling so it does not automatically stage the entire
src.parent directory when a YAML config resides in a broad or oversized
location. Restrict auto-staging to dedicated/safe config directories, or require
explicit confirmation/handling for directories exceeding the size or depth
threshold, while preserving relative-reference resolution for accepted configs
and avoiding unrelated files in the persistent cache.
modelconverter/__main__.py (1)

802-815: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

cache clean has no confirmation before deleting downloaded models/datasets.

cache_clean() immediately shutil.rmtrees the entire cache, including models/ and calibration_data/, which may be expensive to re-download. A confirmation prompt (or a --force/-y flag to skip it) would guard against accidental data loss from a single fat-fingered command.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@modelconverter/__main__.py` around lines 802 - 815, Update cache_clean to
require explicit confirmation before calling shutil.rmtree on the cache, while
providing a force/yes option that bypasses the prompt for non-interactive use.
Preserve the existing empty-cache behavior and success messaging, and ensure
deletion only proceeds after confirmation or the force option is enabled.
docker-compose.yaml (1)

8-14: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Missing rootless-Docker warning for manually-exported HOST_UID/HOST_GID.

This comment instructs users to export HOST_UID=$(id -u) HOST_GID=$(id -g), but docker_utils.py's generate_compose_config deliberately omits these variables under rootless Docker — its own docstring explains that chowning in rootless mode maps files to an unmapped sub-uid instead of the host user. A user who follows this file's instructions while running rootless Docker would hit exactly that breakage, with no warning here.

Add a note (or an id -u/rootless detection snippet) warning users on rootless Docker not to export HOST_UID/HOST_GID.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docker-compose.yaml` around lines 8 - 14, Update the HOST_UID/HOST_GID
comment in docker-compose.yaml to warn users running rootless Docker not to
export these variables, since chowning would map outputs to an unmapped sub-UID.
Preserve the existing instructions for non-rootless Docker and keep the warning
adjacent to the variable declarations.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@configs/defaults.yaml`:
- Around line 168-170: Remove the redundant commented-out “# batch_size: 8” line
immediately before the active batch_size setting, leaving the calibration
batch_size configuration unchanged.
- Around line 42-58: Update the calibration comments near the
stage/output/script fields to describe cross-stage linking as flat sibling
properties under calibration, not as a nested link section. Remove or revise the
“link section” wording while preserving the existing field descriptions and
required-field guidance.

In `@docker/hailo/entrypoint.sh`:
- Around line 15-35: Update the command execution flow in the entrypoint around
chown_mounts so the wrapper forwards termination signals to the running
/bin/bash or modelconverter child while still waiting for its exit status and
invoking chown_mounts afterward. Apply the same signal-handling and cleanup flow
to the duplicated rvc2 and rvc4 entrypoints, preserving the existing argument
selection and status propagation.

In `@docker/rvc2/entrypoint.sh`:
- Around line 9-29: Update the command execution paths in the entrypoint script
to preserve PID 1 signal forwarding by replacing the non-exec launches of bash
and modelconverter with exec-based execution. Keep the existing status capture
and chown_mounts cleanup behavior for both interactive and conversion modes,
ensuring cleanup still runs after the child exits.

In `@docker/rvc4/entrypoint.sh`:
- Around line 17-37: Update the command execution paths in the entrypoint script
to use exec for both the interactive bash branch and the modelconverter branch,
while preserving the existing exit-status and chown_mounts behavior needed after
normal completion. Ensure modelconverter or bash receives PID 1 signals directly
and chown_mounts still runs before exiting when the command terminates.

In `@modelconverter/utils/input_staging.py`:
- Around line 156-201: Make staging atomic in _stage_file, _stage_ir_pair, and
_stage_dir: copy each artifact into a uniquely named temporary sibling under the
digest directory, then publish it with os.replace only after copying completes.
For directories, use shutil.copytree on the temporary directory before replacing
the final directory; preserve existing cache-hit behavior while preventing
incomplete concurrent or interrupted copies from being treated as valid.

---

Outside diff comments:
In `@modelconverter/cli/utils.py`:
- Around line 90-108: Reset the input base unconditionally at the start of
get_configs by calling set_input_base(None) before processing opts or path.
Preserve the existing set_input_base(path_.parent) behavior for
config-file-relative resolution and all subsequent archive/config handling.

---

Nitpick comments:
In `@docker-compose.yaml`:
- Around line 8-14: Update the HOST_UID/HOST_GID comment in docker-compose.yaml
to warn users running rootless Docker not to export these variables, since
chowning would map outputs to an unmapped sub-UID. Preserve the existing
instructions for non-rootless Docker and keep the warning adjacent to the
variable declarations.

In `@modelconverter/__main__.py`:
- Around line 802-815: Update cache_clean to require explicit confirmation
before calling shutil.rmtree on the cache, while providing a force/yes option
that bypasses the prompt for non-interactive use. Preserve the existing
empty-cache behavior and success messaging, and ensure deletion only proceeds
after confirmation or the force option is enabled.

In `@modelconverter/utils/constants.py`:
- Around line 26-31: Centralize the container mount path used by SHARED_DIR in a
single shared constant, then update input_staging.py, docker_utils.py, and
docker-compose.yaml to reference that same configuration value instead of
hardcoding "/app/shared_with_container". Preserve the existing Docker and
non-Docker directory behavior.

In `@modelconverter/utils/docker_utils.py`:
- Around line 118-119: Replace the hardcoded /app/shared_with_container mount
path in the Docker volume configuration with the shared constant defined in
constants.py, updating the relevant docker utility symbol while preserving the
existing host-path mapping.

In `@modelconverter/utils/input_staging.py`:
- Line 25: Replace the hardcoded path in _CONTAINER_SHARED_DIR with the shared
constant defined in constants.py, and update the related references in
docker_utils.py and docker-compose.yaml to use the same centralized value where
applicable. Preserve the existing /app/shared_with_container behavior while
eliminating duplicated literals.
- Around line 141-146: Update _stage_value’s config handling so it does not
automatically stage the entire src.parent directory when a YAML config resides
in a broad or oversized location. Restrict auto-staging to dedicated/safe config
directories, or require explicit confirmation/handling for directories exceeding
the size or depth threshold, while preserving relative-reference resolution for
accepted configs and avoiding unrelated files in the persistent cache.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 02dfb1c2-8742-46e5-9c1a-3353e17cc74c

📥 Commits

Reviewing files that changed from the base of the PR and between 2cfd67f and 997baff.

📒 Files selected for processing (24)
  • .dockerignore
  • .gitignore
  • README.md
  • configs/defaults.yaml
  • configs/mnist.yaml
  • configs/multistage.yaml
  • configs/resnet18.yaml
  • configs/resnet18_IR.yaml
  • configs/yolov6n.yaml
  • configs/yolov8n_seg.yaml
  • docker-compose.yaml
  • docker/hailo/entrypoint.sh
  • docker/rvc2/entrypoint.sh
  • docker/rvc4/entrypoint.sh
  • modelconverter/__main__.py
  • modelconverter/cli/utils.py
  • modelconverter/utils/calibration_data.py
  • modelconverter/utils/constants.py
  • modelconverter/utils/docker_utils.py
  • modelconverter/utils/filesystem_utils.py
  • modelconverter/utils/input_staging.py
  • tests/conftest.py
  • tests/test_packages/common.py
  • tests/test_utils/test_config.py

coderabbitai[bot]

This comment was marked as resolved.

@coderabbitai coderabbitai Bot mentioned this pull request Jul 27, 2026
return process_nn_archive(target, path_, overrides)
# Resolve files referenced *inside* the config (calibration data,
# scripts, encodings, ...) relative to the config file's directory.
set_input_base(path_.parent)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be called at function entry? Otherwise if path is not set we skip the reset of the input base

# references resolve alongside it, then stage and rewrite absolute local
# references which may live anywhere on the host.
if src.suffix.lower() in _CONFIG_EXTS:
parent_dest = _stage_dir(src.parent, inputs_dir)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could be very problematic if I for example have config in some big directory because in that case it would try to sync this whole big directory to the cache even though we don't really need any of it.
What if we would go the other way around: Check for files that we need to copy, copy only these files and rewrite the paths to these files to container specific paths for the specific modelconv process?

AI proposed solution:
Conceptually, replace this:

parent_dest = _stage_dir(src.parent, inputs_dir)
config_dest = parent_dest / src.name

with:

config_dest = _stage_file(src, inputs_dir)
_rewrite_config_paths(src, config_dest, src.parent, inputs_dir)

_rewrite_config_paths would:

  1. Load the YAML.
  2. Walk only known path-bearing fields.
  3. Resolve relative local values against src.parent.
  4. Stage the resolved path using the existing _stage_value.
  5. Replace that YAML value with the returned /app/shared_with_container/inputs/... path.
  6. Write the rewritten YAML to the staged config location.

Illustrative shape:

_PATH_FIELDS = {"input_model", "input_bin", "path", "script", "encodings"}

def _rewrite_config_paths(value, *, config_dir, inputs_dir, key=None):
    if isinstance(value, dict):
        return {
            field: _rewrite_config_paths(
                item,
                config_dir=config_dir,
                inputs_dir=inputs_dir,
                key=field,
            )
            for field, item in value.items()
        }

    if isinstance(value, list):
        return [
            _rewrite_config_paths(
                item, config_dir=config_dir, inputs_dir=inputs_dir, key=key
            )
            for item in value
        ]

    if key not in _PATH_FIELDS or not isinstance(value, str):
        return value

    if get_protocol(value) != "file":
        return value

    candidate = Path(value).expanduser()
    if not candidate.is_absolute():
        candidate = config_dir / candidate

    if not candidate.exists():
        return value  # Let normal config validation report the missing path.

    return _stage_value(str(candidate.resolve()), inputs_dir) or value

In the real implementation, I would scope path specifically to calibration.path, rather than treating every YAML field named path as a filesystem path. The supported fields should be defined from the config schema and covered by tests for:

  • relative model, calibration directory, script, encodings, and OpenVINO .bin;
  • absolute dependencies outside the config directory;
  • remote URLs left unchanged;
  • unrelated sibling files never copied.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants