-
Notifications
You must be signed in to change notification settings - Fork 177
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
Make LLMModelFactory and VLMModelFactory inits public #226
Conversation
|
||
instance.registerProcessorType("PaliGemmaProcessor", creator: create(PaliGemmaProcessorConfiguration.self, PaligGemmaProcessor.init)) | ||
instance.registerProcessorType("Qwen2VLProcessor", creator: create(Qwen2VLProcessorConfiguration.self, Qwen2VLProcessor.init)) | ||
instance.registerProcessorType("Idefics3Processor", creator: create(Idefics3ProcessorConfiguration.self, Idefics3Processor.init)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This makes sense to me and I agree with your thinking in the description. Two thoughts on improvements:
- this could look more like a table (like the deleted code below) and I think it will be more obvious for people to edit
- perhaps the table should be extracted as a
private let
at the top level -- we can put all the tables at the top of the file so it is even more obvious what people need to edit to register these built in models
I wonder if we would want a way to copy shared
in case you wanted a private registry but starting with the default types? Or perhaps you just use shared
in that case. The use case I am imagining is you have a chat application that can use any of these shared models but you also have a private model in your app. In that case you want a merge and updating the global would be fine.
If you provided a framework / swiftpm library and wanted to register your types perhaps you would have a static method that took a VLMModelFactory
and updated the registries on that. That would allow you to add to a pre-populated registry or an empty one.
So I think we don't need a way to copy()
a registry -- if something comes up later we can revisit.
See what you think about moving the default registry to a table at the top of the file.
See comment in the code -- I think
A base class makes sense, something like Right now the
public func registerProcessorType<T>(
_ type: T.Type,
creator: @Sendable @escaping (
URL,
any Tokenizer
) throws -> any UserInputProcessor So this would be called like this: registry.registerProcessorType(PaligGemmaProcessor.self) { url, tokenizer in
let config = try JSONDecoder().decode(PaliGemmaProcessorConfiguration.self, from: Data(contentsOf: url))
return PaligGemmaProcessor(configuration, tokenizer: tokenizer)
} it looks like
A few concerns:
public func registerProcessorType<T>(
_ type: T.Type,
creator: @Sendable @escaping (
URL,
any Tokenizer
) throws -> T If we can enforce the type name and the type matching like that I think this looks valuable. |
I agree. To clarify my thinking, I would like to add an example code here: While we add new features on // in MLXLMCommon
public class ModelRegistry: @unchecked Sendable {
/// Creates an empty registry.
public init() {
registry = Dictionary()
}
/// Creates a new registry with from given model configurations.
public init(modelConfigurations: [ModelConfiguration]) {
registry = Dictionary(uniqueKeysWithValues: modelConfigurations.map { ($0.name, $0) })
}
private let lock = NSLock()
private var registry: Dictionary<String, ModelConfiguration>
public func register(configurations: [ModelConfiguration]) {
lock.withLock {
for c in configurations {
registry[c.name] = c
}
}
}
public func configuration(id: String) -> ModelConfiguration {
lock.withLock {
if let c = registry[id] {
return c
} else {
return ModelConfiguration(id: id)
}
}
}
public var models: some Collection<ModelConfiguration> & Sendable {
lock.withLock {
return registry.values
}
}
}
// in MLXVLM
class VLMRegistry: ModelRegistry {
/// Shared instance with default model configurations.
public static let shared = ModelRegistry(modelConfigurations: all())
static private func all() -> [ModelConfiguration] {
[
paligemma3bMix448_8bit,
qwen2VL2BInstruct4Bit,
]
}
static public let paligemma3bMix448_8bit = ModelConfiguration(
id: "mlx-community/paligemma-3b-mix-448-8bit",
defaultPrompt: "Describe the image in English"
)
static public let qwen2VL2BInstruct4Bit = ModelConfiguration(
id: "mlx-community/Qwen2-VL-2B-Instruct-4bit",
defaultPrompt: "Describe the image in English"
)
static public let smolvlminstruct4bit = ModelConfiguration(
id: "mlx-community/SmolVLM-Instruct-4bit",
defaultPrompt: "Describe the image in English"
)
}
// in MLXLLM
class LLMRegistry: ModelRegistry {
// Same as MLXVLM
} This is just to avoid code duplication. What do you think? |
Yes, looks great! |
@davidkoski Thanks for the feedback. I want to limit this PR with these changes and it is ready now. I will move |
CI failed on the swift-format check -- can you please run: |
@davidkoski Done |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you for the additions!
This PR makes LLMModelFactory and VLMModelFactory inits public.
The PR currently not ready and I want to discuss some cases:
shared
instance on registry types. Is it a good approach? First, I thoughtinit()
should create default registry but I think it isn't a good approach because people going to expect an empty registry.ModelRegistry
class which is same. I think we should merge these two to one on MLXLMCommon package. Maybe, we can make it as a base class and createLLMRegistry
andVLMRegistry
class separately as a subclass ofModelRegistry
. (Same forModelTypeRegistry
)String(describing:)
forProcessorTypeRegistry
registration. For example,This is just for showing but if we use
String(describing:)
it will prevent typos and make the library more type safe IMO.In conclusion, I want to be sure is this approach is acceptable? If so, I will do the same for the remaining parts.