Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[feat:] Parallelize Layer Packing and Unpacking #525

Open
wants to merge 8 commits into
base: main
Choose a base branch
from

Conversation

SkySingh04
Copy link

Overview:

This PR addresses issue #254, which seeks to optimize the pack and unpack commands by parallelizing the processing of layers. Previously, both commands handled layers sequentially, which slowed down operations significantly, especially with large files and multiple layers. This enhancement introduces multithreading for both packing and unpacking, leading to improved performance and efficiency.

  • Parallel Layer Packing: The packing process has been modified to handle each layer (model parts, code, datasets, documentation) concurrently using goroutines. A sync.WaitGroup is used to ensure that all layers are processed before the operation completes, and a mutex guarantees thread-safe access to shared data structures.

  • Parallel Layer Unpacking: Similarly, unpacking is now processed in parallel. Layers are unpacked concurrently, reducing the time required for large models with multiple layers. The runUnpackRecursive function has been updated to handle this using goroutines and error channels to ensure safe and efficient operation.

Key Changes:

  1. Parallelization in both saveKitfileLayers (packing) and runUnpackRecursive (unpacking) commands:
    • Introduced concurrency using goroutines for each layer type (model, code, datasets, documentation).
    • Synchronized the process using sync.WaitGroup and sync.Mutex to ensure thread-safe operations.
    • Error handling is improved by collecting errors from concurrent goroutines using error channels.

This PR resolves issue #254 by introducing multithreading for both pack and unpack commands, offering a more efficient and faster solution for handling large models with multiple layers.

Signed-off-by: Akash Singh <akashsingh2210670@gmail.com>
Signed-off-by: Akash Singh <akashsingh2210670@gmail.com>
@SkySingh04
Copy link
Author

@amisevsk @gorkem Kindly review this PR and suggest any necessary changes!

Signed-off-by: Akash Singh <akashsingh2210670@gmail.com>
Copy link
Contributor

@gorkem gorkem left a comment

Choose a reason for hiding this comment

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

Looking good already

pkg/lib/kitfile/local-storage.go Show resolved Hide resolved
pkg/cmd/unpack/unpack.go Show resolved Hide resolved
Signed-off-by: Akash Singh <akashsingh2210670@gmail.com>
@SkySingh04
Copy link
Author

@gorkem Your requested changes were made

Copy link
Contributor

@amisevsk amisevsk left a comment

Choose a reason for hiding this comment

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

Thanks for the contribution! Looks great so far.

I have a couple of concerns, mostly in the packing process; we need the generated OCI manifest to match the (per-type) order in the Kitfile (fixing this is something I'm considering for #489)

pkg/lib/kitfile/local-storage.go Outdated Show resolved Hide resolved
pkg/lib/kitfile/local-storage.go Outdated Show resolved Hide resolved
Comment on lines 107 to 109
mu.Lock()
layers = append(layers, layer)
mu.Unlock()
Copy link
Contributor

Choose a reason for hiding this comment

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

I think this will cause some issues; currently the only way we have to match layer descriptors is through ordering, and this can result in a varying order for layers.

We'll need to come up with an approach that ensures the layers list matches the order of elements in the kitfile (i.e. all datasets in the same order, etc.)

Copy link
Contributor

Choose a reason for hiding this comment

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

What I mean here is that in the unpack process, iterate through each of the lists of items in the kitfile, matching indices (the second dataset we see in the manifest should be the second dataset in the kitfile)

With this approach, if, for example, we first pack a large dataset, then pack a smaller dataset, the smaller dataset may be added to layers before the large one.

Copy link
Author

Choose a reason for hiding this comment

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

What we can do to solve this is to implement a simple layer indexing logic. A simple counter layerIndex can be incremented after each item is added to maintain the order.
Each goroutine is given a layerIndex that corresponds to its position in the Kitfile, ensuring the order is preserved when layers are added to the layers slice.

Copy link
Contributor

Choose a reason for hiding this comment

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

Good idea, this should work 👍

SkySingh04 and others added 2 commits October 11, 2024 00:52
Co-authored-by: Angel Misevski <misevskia@gmail.com>
Signed-off-by: Akash Singh <akashsingh2210670@gmail.com>
@SkySingh04
Copy link
Author

@amisevsk Your requested changes have been made

Signed-off-by: Akash <akashsingh2210670@gmail.com>
Copy link
Contributor

@amisevsk amisevsk left a comment

Choose a reason for hiding this comment

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

The PR looks good, but you'll see there's test failures in the CI.

This is because the current filesystem.IgnorePaths uses PatternMatcher, which is not thread-safe.

We can maybe work around this by implementing an IgnorePaths.Clone() method to not share the patternmatcher between the threads, but I haven't tested it out.

To run the failing tests locally, use go test ./testing/...

@SkySingh04
Copy link
Author

Hey @amisevsk , I have implemented a IgnorePaths.Clone() method to not share the patternmatcher between the threads but my tests are still failing. I am a little stuck here, could you please point me to the right direction?

here is the relevant log:

--- FAIL: TestPackUnpack (0.99s)
    --- FAIL: TestPackUnpack/test_pack-ignores-basic.yaml_(Pack_and_unpack_with_basic_ignorefile) (0.15s)
        util_test.go:100: Using temp directory: C:\Users\USER\AppData\Local\Temp\kitops-testing-1608491781
        util_test.go:143: creating path C:\Users\USER\AppData\Local\Temp\kitops-testing-1608491781\test-modelkit-in\dir2\file2.txt
        util_test.go:143: creating path C:\Users\USER\AppData\Local\Temp\kitops-testing-1608491781\test-modelkit-in\dir2\dirB\fileB.txt
        util_test.go:143: creating path C:\Users\USER\AppData\Local\Temp\kitops-testing-1608491781\test-modelkit-in\root-included.txt
        util_test.go:143: creating path C:\Users\USER\AppData\Local\Temp\kitops-testing-1608491781\test-modelkit-in\dir3\dir3-included.txt
        util_test.go:143: creating path C:\Users\USER\AppData\Local\Temp\kitops-testing-1608491781\test-modelkit-in\dir3\dirA\fileA.txt
        util_test.go:143: creating path C:\Users\USER\AppData\Local\Temp\kitops-testing-1608491781\test-modelkit-in\dir4\dirA\not-ignored
        util_test.go:143: creating path C:\Users\USER\AppData\Local\Temp\kitops-testing-1608491781\test-modelkit-in\dir4\dirB\not-ignored
        util_test.go:143: creating path C:\Users\USER\AppData\Local\Temp\kitops-testing-1608491781\test-modelkit-in\dir4\ignored-file.txt
        util_test.go:143: creating path C:\Users\USER\AppData\Local\Temp\kitops-testing-1608491781\test-modelkit-in\dir5\not-ignored.md
        util_test.go:143: creating path C:\Users\USER\AppData\Local\Temp\kitops-testing-1608491781\test-modelkit-in\dir1\dirA\fileA.txt
        util_test.go:143: creating path C:\Users\USER\AppData\Local\Temp\kitops-testing-1608491781\test-modelkit-in\dir1\file1.txt
        util_test.go:143: creating path C:\Users\USER\AppData\Local\Temp\kitops-testing-1608491781\test-modelkit-in\dir2\dirA\fileA.txt
        util_test.go:143: creating path C:\Users\USER\AppData\Local\Temp\kitops-testing-1608491781\test-modelkit-in\root-ignored.txt
        util_test.go:143: creating path C:\Users\USER\AppData\Local\Temp\kitops-testing-1608491781\test-modelkit-in\dir3\dir3-ignored.txt
        util_test.go:143: creating path C:\Users\USER\AppData\Local\Temp\kitops-testing-1608491781\test-modelkit-in\dir4\dirA\ignored-file.txt
        util_test.go:143: creating path C:\Users\USER\AppData\Local\Temp\kitops-testing-1608491781\test-modelkit-in\dir4\dirB\ignored-file.txt
        util_test.go:143: creating path C:\Users\USER\AppData\Local\Temp\kitops-testing-1608491781\test-modelkit-in\dir4\dirC\ignored-file.txt
        util_test.go:143: creating path C:\Users\USER\AppData\Local\Temp\kitops-testing-1608491781\test-modelkit-in\ignored-root.md
        util_test.go:143: creating path C:\Users\USER\AppData\Local\Temp\kitops-testing-1608491781\test-modelkit-in\dir2\ignored-dir2.md
        util_test.go:143: creating path C:\Users\USER\AppData\Local\Temp\kitops-testing-1608491781\test-modelkit-in\dir3\dirA\ignored-dirA.md
        util_test.go:143: creating path C:\Users\USER\AppData\Local\Temp\kitops-testing-1608491781\test-modelkit-in\dir2\ignored\ignored-file1.txt
        util_test.go:143: creating path C:\Users\USER\AppData\Local\Temp\kitops-testing-1608491781\test-modelkit-in\dir3\dirB\ignored\ignored-file2.txt    
        util_test.go:143: creating path C:\Users\USER\AppData\Local\Temp\kitops-testing-1608491781\test-modelkit-in\ignored-a.txt
        util_test.go:143: creating path C:\Users\USER\AppData\Local\Temp\kitops-testing-1608491781\test-modelkit-in\ignored-b.txt
        util_test.go:143: creating path C:\Users\USER\AppData\Local\Temp\kitops-testing-1608491781\test-modelkit-in\ignored-1.txt
        util_test.go:65: Running command: kit pack C:\Users\USER\AppData\Local\Temp\kitops-testing-1608491781\test-modelkit-in -t test:test -vvv
        util_test.go:79: Command output:
            [DEBUG] Using config directory from environment variable: C:\Users\USER\AppData\Local\Temp\kitops-testing-1608491781\.kitops
            [DEBUG] Using storage path: C:\Users\USER\AppData\Local\Temp\kitops-testing-1608491781\.kitops\storage
            [DEBUG] Context dir: C:\Users\USER\AppData\Local\Temp\kitops-testing-1608491781\test-modelkit-in
            [DEBUG] Model file: C:\Users\USER\AppData\Local\Temp\kitops-testing-1608491781\test-modelkit-in\Kitfile
            [DEBUG] Packing localhost/test:test
            [INFO ] Saved configuration: sha256:914cb4a5cf822777b563a633bb4cf55730d0b839cd893dbe60d59b9c7e8966f8
            [DEBUG] Compressing layer to temporary file C:\Users\USER\AppData\Local\Temp\kitops_layer_4137378936
            [DEBUG] Skipping file .kitignore: ignored
            [DEBUG] Skipping file Kitfile: ignored
            [DEBUG] Skipping directory dir1: ignored
            [DEBUG] Wrote header dir2 to tar file
            [DEBUG] Skipping directory dir2\dirA: ignored
            [DEBUG] Wrote header dir2/dirB to tar file
            [DEBUG] Wrote header dir2/dirB/fileB.txt to tar file
            [DEBUG] Wrote file dir2\dirB\fileB.txt to tar file
            [DEBUG] Wrote header dir2/file2.txt to tar file
            [DEBUG] Wrote file dir2\file2.txt to tar file
            [DEBUG] Skipping directory dir2\ignored: ignored
            [DEBUG] Skipping file dir2\ignored-dir2.md: ignored
            [DEBUG] Wrote header dir3 to tar file
            [DEBUG] Skipping file dir3\dir3-ignored.txt: ignored
            [DEBUG] Wrote header dir3/dir3-included.txt to tar file
            [DEBUG] Wrote file dir3\dir3-included.txt to tar file
            [DEBUG] Wrote header dir3/dirA to tar file
            [DEBUG] Wrote header dir3/dirA/fileA.txt to tar file
            [DEBUG] Wrote file dir3\dirA\fileA.txt to tar file
            [DEBUG] Skipping file dir3\dirA\ignored-dirA.md: ignored
            [DEBUG] Wrote header dir3/dirB to tar file
            [DEBUG] Skipping directory dir3\dirB\ignored: ignored
            [DEBUG] Wrote header dir4 to tar file
            [DEBUG] Wrote header dir4/dirA to tar file
            [DEBUG] Skipping file dir4\dirA\ignored-file.txt: ignored
            [DEBUG] Wrote header dir4/dirA/not-ignored to tar file
            [DEBUG] Wrote file dir4\dirA\not-ignored to tar file
            [DEBUG] Wrote header dir4/dirB to tar file
            [DEBUG] Skipping file dir4\dirB\ignored-file.txt: ignored
            [DEBUG] Wrote header dir4/dirB/not-ignored to tar file
            [DEBUG] Wrote file dir4\dirB\not-ignored to tar file
            [DEBUG] Wrote header dir4/dirC to tar file
            [DEBUG] Skipping file dir4\dirC\ignored-file.txt: ignored
            [DEBUG] Wrote header dir4/ignored-file.txt to tar file
            [DEBUG] Wrote file dir4\ignored-file.txt to tar file
            [DEBUG] Wrote header dir5 to tar file
            [DEBUG] Skipping file dir5\not-ignored.md: ignored
            [DEBUG] Skipping file ignored-1.txt: ignored
            [DEBUG] Skipping file ignored-a.txt: ignored
            [DEBUG] Skipping file ignored-b.txt: ignored
            [DEBUG] Skipping file ignored-root.md: ignored
            [DEBUG] Skipping file root-ignored.txt: ignored
            [DEBUG] Wrote header root-included.txt to tar file
            [DEBUG] Wrote file root-included.txt to tar file
            [INFO ] Saved model layer: sha256:59842449b1f3bbd5ae91565d9a7070716e5c824d35e12a9e58545bc5c265dcf1
            [INFO ] Saved manifest to storage: sha256:ffa47831f1eeb064691c0c8989e30c782cb1de9124540221520edb4a2bb322e3
            [DEBUG] Added tag to manifest: test
            [INFO ] Model saved: sha256:ffa47831f1eeb064691c0c8989e30c782cb1de9124540221520edb4a2bb322e3
        util_test.go:65: Running command: kit list -vvv
        util_test.go:79: Command output:
            [DEBUG] Using config directory from environment variable: C:\Users\USER\AppData\Local\Temp\kitops-testing-1608491781\.kitops
            REPOSITORY   TAG    MAINTAINER   NAME           SIZE       DIGEST
            test         test   <none>       test-ignores   14.0 KiB   sha256:ffa47831f1eeb064691c0c8989e30c782cb1de9124540221520edb4a2bb322e3
        util_test.go:65: Running command: kit unpack test:test -d C:\Users\USER\AppData\Local\Temp\kitops-testing-1608491781\test-modelkit-out -vvv        
        util_test.go:79: Command output:
            [DEBUG] Using config directory from environment variable: C:\Users\USER\AppData\Local\Temp\kitops-testing-1608491781\.kitops
            [DEBUG] Overwrite: false
            [DEBUG] Unpacking localhost/test:test
            [INFO ] Unpacking to C:\Users\USER\AppData\Local\Temp\kitops-testing-1608491781\test-modelkit-out
            [INFO ] Unpacking config to C:\Users\USER\AppData\Local\Temp\kitops-testing-1608491781\test-modelkit-out\Kitfile
            [INFO ] Unpacking model test-ignores to .
            [DEBUG] Creating directory dir2
            [DEBUG] Creating directory dir2\dirB
            [DEBUG] Unpacking file dir2\dirB\fileB.txt
            [DEBUG] Unpacking file dir2\file2.txt
            [DEBUG] Creating directory dir3
            [DEBUG] Unpacking file dir3\dir3-included.txt
            [DEBUG] Creating directory dir3\dirA
            [DEBUG] Unpacking file dir3\dirA\fileA.txt
            [DEBUG] Creating directory dir3\dirB
            [DEBUG] Creating directory dir4
            [DEBUG] Creating directory dir4\dirA
            [DEBUG] Unpacking file dir4\dirA\not-ignored
            [DEBUG] Creating directory dir4\dirB
            [DEBUG] Unpacking file dir4\dirB\not-ignored
            [DEBUG] Creating directory dir4\dirC
            [DEBUG] Unpacking file dir4\ignored-file.txt
            [DEBUG] Creating directory dir5
            [DEBUG] Unpacking file root-included.txt
            [DEBUG] Unpacked 0 model part layers
            [DEBUG] Unpacked 0 code layers
            [DEBUG] Unpacked 0 dataset layers
            [DEBUG] Unpacked 0 docs layers
        util_test.go:158: File dir5/not-ignored.md should exist
        util_test.go:97: Error removing temp dir: remove C:\Users\USER\AppData\Local\Temp\kitops-testing-1608491781\test-modelkit-in\.kitignore: The process cannot access the file because it is being used by another process.
FAIL
FAIL    kitops/testing  3.394s
FAIL

@SkySingh04
Copy link
Author

@amisevsk @gorkem Waiting for a response on this so that we can proceed further!

@amisevsk
Copy link
Contributor

amisevsk commented Oct 21, 2024

@SkySingh04 There's a bug somewhere in the pack/unpack logic. The test in question is creating a file dir5/not-ignored.md and packing it. When it unpacks that same modelkit, the file appears to be missing, suggesting it was never packed.

If you'd like to reproduce the issue locally (and are using a Unix-like OS), you can use the following bash script (Note this script depends on https://github.com/kislyuk/yq)

#!/bin/bash

TEMP_DIR=./TEMP
TEMP_UNPACK_DIR=./TEMP_UNPACK
mkdir -p $TEMP_DIR

for file in $(yq -r '.ignored + .files | .[]' testing/testdata/pack-unpack/test_pack-ignores-basic.yaml); do
  mkdir -p "$TEMP_DIR/$(dirname $file)"
  echo $file > $TEMP_DIR/$file
done

yq -r '.kitfile' testing/testdata/pack-unpack/test_pack-ignores-basic.yaml > $TEMP_DIR/Kitfile
yq -r '.kitignore' testing/testdata/pack-unpack/test_pack-ignores-basic.yaml > $TEMP_DIR/.kitignore

./kit pack -t test:latest $TEMP_DIR
./kit unpack test:latest -d $TEMP_UNPACK_DIR

(this basically reproduces what the test does locally -- you can see that TEMP_UNPACK does not contain dir5)

If you'd like to step-debug the issue, you can replace ./kit with dlv debug --listen=:2345 --headless=true --api-version=2 ./main.go -- and connect a debugger (e.g. VS Code)

dlv debug --listen=:2345 --headless=true --api-version=2 ./main.go -- pack -t test:latest TEMP

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

Successfully merging this pull request may close these issues.

4 participants