Skip to content

prepare for new mlx / mlx-c release#227

Merged
davidkoski merged 1 commit intomlx0_24_2from
import_functions
May 1, 2025
Merged

prepare for new mlx / mlx-c release#227
davidkoski merged 1 commit intomlx0_24_2from
import_functions

Conversation

@davidkoski
Copy link
Collaborator

@davidkoski davidkoski commented Apr 24, 2025

let data = asDataCopy()
return data.data.withUnsafeBytes { ptr in
device.makeBuffer(bytes: ptr.baseAddress!, length: ptr.count)
}
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Unrelated but per question the non-contiguous case wasn't actually making contiguous storage.

/// specialized conversion between integer types -- see ``item(_:)``
private func itemInt() -> Int {
precondition(self.size == 1)
eval()
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Now includes an explicit eval in order to use the evalLock

mlx_array_eval(ctx)
_ = evalLock.withLock {
mlx_array_eval(ctx)
}
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

All eval, asyncEval, Stream need to take a lock -- new version of mlx-c puts more work in the calling thread and it needs to be guarded. To be revisited in the future.

outputShapes: [[Int]],
outputDTypes: [DType],
initValue: Float? = nil,
verbose: Bool = false
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

This reverts the API change from #150 -- the new mlx-c API allows us to match the old calls (and python). See #211

/// - stride: kernel stride
/// - padding: input padding
/// - dilation: kernel dilation
/// - outputPadding: output padding
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

The convTranspose functions pick up an outputPadding (optional)

public func gatherMatmul(
_ a: MLXArray, _ b: MLXArray, lhsIndices: MLXArray? = nil, rhsIndices: MLXArray? = nil,
stream: StreamOrDevice = .default
sortedIndices: Bool = false, stream: StreamOrDevice = .default
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

gather_mm takes a hint for sorted indices


deinit {
mlx_stream_free(ctx)
_ = evalLock.withLock {
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Streams need to use the same lock as they manipulate the same shared structures.

fatalError(message.map { String(cString: $0) } ?? "")
}

/// Evaluate the block with a scoped MLX error handler.
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

New error handling capabilities -- the import/export code requires this to correctly catch errors from mlx::core and this functionality is generally useful for swift code that wants to catch errors from core rather than fatally assert.

///
/// ### See Also
/// - ``exportFunctions(to:shapeless:_:export:)``
/// - ``importFunction(from:)``
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

New export/import capability -- the example code is from the tests.

mlx_load(&result, path.cString(using: .utf8), stream.ctx)
_ = try withError {
mlx_load(&result, path.cString(using: .utf8), stream.ctx)
}
Copy link
Collaborator Author

@davidkoski davidkoski Apr 28, 2025

Choose a reason for hiding this comment

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

These can properly catch an error for a missing file.

Note the _ = is because mlx_load returns an int

///
/// ### See Also
/// - <doc:lazy-evaluation>
public func checkedEval(_ values: Any...) throws {
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

For use when you want eval to throw. Note: this won't catch broadcasting errors as those occur at graph build time, e.g. a + b.

@davidkoski davidkoski marked this pull request as ready for review April 28, 2025 20:44
public var eps: Double {
switch dtype {
case .float16: Double(Float16.ulpOfOne)
#if !arch(x86_64)
Copy link
Member

Choose a reason for hiding this comment

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

Why just the fp16 guard here?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I ran into this when building apps that depended on it -- Float16 isn't a thing on x86. We don't see it in framework builds, but by default App builds do all architectures when building Release so we encounter this (even though that slice won't run)

Copy link
Member

Choose a reason for hiding this comment

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

Right but presumably bfloat16 isn't a thing on x86 Macs as well. I guess the difference is we hardcode the bf16 values here.

but by default App builds do all architectures when building Release

I wonder how the rest of MLX builds in that case? I think MLX fp16 runs on x86 using the software implementation -- which means we should probably hardcode the values here too?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

BFloat isn't a type in swift so this one doesn't come up.

Sure, we could hard code the values here for x86 -- is that a supported configuration? Easy enough to do.

Copy link
Member

Choose a reason for hiding this comment

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

is that a supported configuration?

I mean it's not common.. so this is kind of minor.

Linux x86 is supported and we run those tests regularly. macOS x86 is very uncommon.

Hence I also think it's fine to leave it like this and if anyone asks for that we can add it back.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Added!

/// - url: file url to write the `.mlxfn` file
/// - shapeless: if `true` the function allows inputs with variable shapes
/// - f: the function to capture
/// - Returns: a help that records the call
Copy link
Member

Choose a reason for hiding this comment

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

The return comment there is unclear. What is a "help"?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

should be "helper" -- I will clarify

/// - url: file url to write the `.mlxfn` file
/// - shapeless: if `true` the function allows inputs with variable shapes
/// - f: the function to capture
/// - export: closure for recording the calls
Copy link
Member

Choose a reason for hiding this comment

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

Looks like that parameter doesn't exist?

Copy link
Member

Choose a reason for hiding this comment

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

Or maybe it's supposed to be a return?

Copy link
Member

Choose a reason for hiding this comment

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

I guess I'm wrong about that.. but I'm trying to decipher how the export parameter works.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

        try exportFunctions(to: url, shapeless: true, f) { export in
            try export(x)
            try export(x, x)
            try export(x, x, x)
        }

export is the trailing closure -- the { export in ... }

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

and the export parameter in the closure is a FunctionExporterMultiple

Copy link
Member

Choose a reason for hiding this comment

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

That's makes sense now.. but maybe is slightly confusing. Because the export parameter is both the closure and the name of the parameter to the closure. But if it's a common idiom then let's go for it.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

You don't normally see the name of the closure parameter (but you can). Let me think if there is a more expressive name. If not, I think nobody will notice as the trailing closure syntax is idiomatic.

Comment on lines +211 to +218
/// // f2 is a callable that represents the loaded function
/// let f2 = try importFunction(from: url)
///
/// let a = MLXArray(10)
/// let b = MLXArray([5, 10, 20])
///
/// // call it -- the shapes and labels have to match
/// let r = try f2(a, y: b)[0]
Copy link
Member

@awni awni Apr 30, 2025

Choose a reason for hiding this comment

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

Nit: name the function f rather than f2 as there is no f1

Copy link
Member

@awni awni left a comment

Choose a reason for hiding this comment

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

Looks awesome. Left a few nits and some questions, please take a look. Otherwise I think this is good to go!

@davidkoski
Copy link
Collaborator Author

Looks awesome. Left a few nits and some questions, please take a look. Otherwise I think this is good to go!

Thank you @awni -- appreciated as always!

- fixes #211 (moves metalKernel API back to what it was)
- add improved swift error handling
- add function import/export

- adopt mlx v0.25.1 and mlx-c v0.2.0
- fixes #211 (moves metalKernel API back to what it was)
- use an evalLock for eval, asyncEval and Stream creation
- add withError and withErrorHandler error handling
- add function import/export: https://ml-explore.github.io/mlx/build/html/usage/export.html
@davidkoski davidkoski merged commit bc923f3 into mlx0_24_2 May 1, 2025
@davidkoski davidkoski deleted the import_functions branch May 1, 2025 15:50
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