Skip to content

Add compression argument to publish() - #65

Open
frankenjoe wants to merge 4 commits into
mainfrom
publish-compression
Open

Add compression argument to publish()#65
frankenjoe wants to merge 4 commits into
mainfrom
publish-compression

Conversation

@frankenjoe

@frankenjoe frankenjoe commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

audeer.create_archive() used to hardcode deflate at zlib's default level 6. Deflate is single threaded at around 10 MB/s on model weights, so it accounted for nearly the whole publication time of a model: 758 s of 776 s for a 7.4 GiB model.

audeer 2.6.0 added a compression argument, and this PR exposes it in audmodel.publish():

audmodel.publish(root, name, params, version, compression=0)
  • 0 stores the model files without compression
  • 1-9 selects a deflate level

The level is validated in publish() before anything is uploaded, as the cleanup handler around the upload would otherwise replace the error with "Could not publish model due to an unexpected error."

audeer >=2.6.0 is added to dependencies.

Up for discussion: the default

This PR defaults to compression=1.

Measurements on a 7.38 GiB model with 16 files, archive written to and read from local disk:

default create extract archive size
6 (behaviour before) 758.5 s 66.8 s 5.60 GiB
1 (this PR) 316.6 s 69.9 s 5.65 GiB
0 17.3 s 17.2 s 7.38 GiB

The alternatives:

  • None, keep the previous behaviour.
    Would need the type widened to int | None, with None forwarded to audeer, which then picks zlib's level 6. Disadvantage: level 6 is a bad operating point on model weights: it compresses 0.8 pp better than level 1 and takes 2.4x as long.
  • 0, no compression by default.
    Compressing at level 1 costs 299 s to save 1.73 GiB, so it only pays off below ~5.9 MB/s (~50 Mbit/s) of upload bandwidth.
  • 1, the middle way, as implemented.
    An archive is written once but downloaded many times. On the load side inflating costs 53 s to save the same 1.73 GiB, break even at ~34 MB/s (~270 Mbit/s) of download bandwidth, so for most consumers level 1 is at worst neutral. It also keeps the storage on the backend at today's size.

audeer.create_archive() used to hardcode deflate at zlib's
default level 6, which is single threaded at around 10 MB/s
and thereby accounted for nearly the whole publication time
of a model, e.g. 758 s of 776 s for a 7.4 GiB model.

audmodel.publish() now exposes the compression level added in
audeer 2.6.0, and defaults it to 1. On model weights level 1
compresses as well as level 6, 0.8 pp apart, at 2.4x the speed,
and 0 stores the files and makes publication 44x faster than
before at the price of a 24% larger archive.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@sourcery-ai

sourcery-ai Bot commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

Reviewer's Guide

Adds a validated compression option to audmodel.publish(), threads it through archive creation, and defaults new publications to deflate level 1 to reduce publication time while retaining current storage and compatibility characteristics. Runtime dependency requirements and publish tests are updated accordingly.

Sequence diagram for publishing a model with configurable compression

sequenceDiagram
    participant Caller
    participant Publish as audmodel.publish()
    participant Archive as put_archive()
    participant Audeer as audeer.create_archive()
    participant Backend

    Caller->>Publish: publish(compression)
    Publish->>Publish: validate compression 0..9
    Publish->>Archive: put_archive(compression=compression)
    Archive->>Audeer: create_archive(compression=compression)
    Audeer-->>Archive: model.zip
    Archive->>Backend: upload model.zip
    Backend-->>Caller: published model
Loading

File-Level Changes

Change Details Files
Expose configurable ZIP compression for model publication, defaulting to deflate level 1.
  • Add the public compression argument with documentation and a default of 1.
  • Validate levels 0–9 before filesystem checks or uploads.
  • Propagate the selected level through archive creation to audeer.create_archive().
  • Preserve existing archive naming and backend layout while allowing stored entries at level 0.
audmodel/core/api.py
audmodel/core/backend.py
Promote the required archive library version to the runtime dependency set.
  • Require audeer >=2.6.0 for the new compression API.
  • Remove the outdated lower version from development-only dependencies.
pyproject.toml
Add coverage for default, explicit, and invalid compression settings.
  • Verify default and deflate levels produce compressed ZIP entries, while level 0 produces stored entries.
  • Verify out-of-range levels fail with the documented ValueError.
tests/test_publish.py

Possibly linked issues

  • #unknown: The PR implements the requested compression feature and supports uncompressed archives via compression=0, despite a different default.

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@codecov

codecov Bot commented Sep 4, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 100.0%. Comparing base (0aad717) to head (9240f05).

Additional details and impacted files
Files with missing lines Coverage Δ
audmodel/core/api.py 100.0% <100.0%> (ø)
audmodel/core/backend.py 100.0% <ø> (ø)
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@sourcery-ai sourcery-ai 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.

Hey - I've found 1 issue

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location path="audmodel/core/backend.py" line_range="393-394" />
<code_context>
     repository: Repository,
     alias: str | None = None,
     author: str | None = None,
+    compression: int = 1,
     date: datetime.date | None = None,
     meta: dict[str, object] | None = None,
</code_context>
<issue_to_address>
**issue (bug_risk):** Adding `compression` before `tmp_root` changes the positional calling convention of `put_archive()`. Existing callers that pass `tmp_root` as the eighth positional argument now supply that path as `compression`, causing archive creation to fail instead of using the requested temporary directory.

**Triggers:** When downstream code calls the internal backend helper positionally with a `tmp_root` argument.

**Suggested fix:** Append `compression` after `tmp_root`, or make the new parameter keyword-only while preserving the existing positional parameters.

```suggestion
    tmp_root: str | None = None,
    compression: int = 1,
```
</issue_to_address>

Sourcery assessment

Needs a human reviewer. 1 finding to address first, and an incorrect compression setting is recorded in published archives, affecting their storage size and download/decompression behavior; reverting only affects future publications. Existing archives can be replaced or republished, so the impact is bounded and repairable.

Blocking findings: audmodel/core/backend.py:394


Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread audmodel/core/backend.py Outdated
frankenjoe and others added 2 commits September 4, 2026 11:40
put_archive() is internal,
and publish() is its only caller,
so the argument does not need a default.
This also keeps tmp_root the last argument,
and the only one with a default.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Changes recommended

The new compression validation can raise TypeError for non-numeric inputs (instead of the intended ValueError), and the new test stores inconsistent params metadata when the compression argument is omitted.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

This PR exposes audeer.create_archive()’s new compression option through audmodel.publish(), allowing callers to trade off publication speed vs. archive size and ensuring audeer>=2.6.0 is available at runtime.

Changes:

  • Add compression keyword argument to audmodel.publish() (defaulting to level 1) and validate it early.
  • Thread compression through the backend upload path into audeer.create_archive().
  • Add tests covering compression behavior and invalid compression levels; update dependencies to require audeer>=2.6.0.
File summaries
File Description
audmodel/core/api.py Adds compression argument, docs, and pre-upload validation; forwards to backend archiving.
audmodel/core/backend.py Extends put_archive() to accept/pass compression into audeer.create_archive().
tests/test_publish.py Adds coverage for archive compression mode and invalid compression levels.
pyproject.toml Moves audeer into runtime deps and bumps minimum version to >=2.6.0.
Review details
  • Files reviewed: 4/4 changed files
  • Comments generated: 2
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread audmodel/core/api.py
Comment thread tests/test_publish.py
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Comment thread audmodel/core/api.py
Comment on lines +513 to +527
compression: compression level
of the model archive.
``0`` stores the model files
without compression,
``1``-``9`` selects a deflate level.
Deflate is single threaded,
and dominates the publication time
of large models.
Higher levels than ``1``
hardly compress better
on model weights,
but take at least twice as long.
Select ``0``,
if you want to publish
as fast as possible

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

As higher levels as 1 do not make sense, we could even simplify to compression: bool = True.

Otherwise, we should maybe also support None as input to make it equal to audeer.create_archive(). Staying at 1 as default instead of None makes sense.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

In fact a user could already pass False -> 0 and True -> 1 if she wanted to do so. Yet, generally restricting to a boolean I find a bit too limitating. There might still be models (e.g. with large text files) where a higher compression rate can still make sense.

we should maybe also support None

If we allow None it should default to a reasonable value, but audeer maps it to library default, which is 6, and according to the benchmark this is bad choice.

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.

3 participants