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

Prefer chat_template.json for chat template #184

Merged
merged 2 commits into from
Feb 27, 2025

Conversation

DePasqualeOrg
Copy link
Contributor

@FL33TW00D
Copy link
Collaborator

Looks good to me @pcuenca

@pcuenca
Copy link
Member

pcuenca commented Feb 26, 2025

Taking a look now

Copy link
Member

@pcuenca pcuenca 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 another great contribution @DePasqualeOrg!

There are a couple of subtleties about chat templates that I'm willing to ignore (unless I'm missing something important), and some minor code changes that I think could make the code a bit cleaner – although this could admittedly be subjective.

Comment on lines 208 to 214
let tokenizerVocab = try hubApi.configuration(fileURL: modelFolder.appending(path: "tokenizer.json"))
let configs = Configurations(
modelConfig: modelConfig,
tokenizerConfig: updatedConfig,
tokenizerData: tokenizerVocab
)
return configs
Copy link
Member

Choose a reason for hiding this comment

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

Not a fan of repeating this code block and the return here, inside the nested ifs.

I'd recommend we write a helper function to potentially update the chat template, such as:

    func updatedTokenizerConfig(tokenizerConfig: Config?, chatTemplateConfig: Config?) -> Config? {
        guard
            let chatTemplateConfig = chatTemplateConfig,
            let overrideChatTemplate = chatTemplateConfig.chatTemplate?.stringValue else {
            return tokenizerConfig
        }

        var configDict = tokenizerConfig?.dictionary ?? [:]
        configDict["chat_template"] = overrideChatTemplate
        return Config(configDict)
    }

And then we can just use this before the return:

        let configs = Configurations(
            modelConfig: modelConfig,
            tokenizerConfig: updatedTokenizerConfig(tokenizerConfig: tokenizerConfig, chatTemplateConfig: chatTemplateConfig),
            tokenizerData: tokenizerVocab
        )

Comment on lines 214 to 215
XCTAssertTrue(qwen2VLEncoded == qwen2_5VLEncoded)
XCTAssertTrue(qwen2VLDecoded == qwen2_5VLDecoded && qwen2_5VLDecoded == expectedOutput)
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
XCTAssertTrue(qwen2VLEncoded == qwen2_5VLEncoded)
XCTAssertTrue(qwen2VLDecoded == qwen2_5VLDecoded && qwen2_5VLDecoded == expectedOutput)
XCTAssertEqual(qwen2VLEncoded, qwen2_5VLEncoded, "Encoded sequences should be equal")
XCTAssertEqual(qwen2VLDecoded, qwen2_5VLDecoded, "Decoded sequences should be equal")
XCTAssertEqual(qwen2_5VLDecoded, expectedOutput, "Decoded should match expected")

nit, should provide better error messages maybe

Comment on lines 195 to 196
// Check for chat_template.json, which contains the preferred chat template for vision language models
if let chatTemplateConfig = try? hubApi.configuration(fileURL: modelFolder.appending(path: "chat_template.json")) {
Copy link
Member

Choose a reason for hiding this comment

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

Technically, this is not the same algorithm used in transformers. IIRC, if we instantiate a tokenizer from a repo where the tokenizer has a chat template and a different chat_template.json, the template from the tokenizer will still be used. However, if we instantiate a processor, then the chat_template.json will be used.

I'm willing to diverge from this behaviour, given that:

  • Chat template divergence should not be expected (it's possibly a mistake if both templates differ)
  • Processor and tokenizer templates should be synced at some point.
  • There is no processor abstraction in swift-transformers.

cc @Rocketknight1 in case I'm missing some weird edge case.

// Check for chat_template.json, which contains the preferred chat template for vision language models
if let chatTemplateConfig = try? hubApi.configuration(fileURL: modelFolder.appending(path: "chat_template.json")) {
// If chat_template.json exists and contains a chat_template field, use it to override the tokenizer_config
if let chatTemplate = chatTemplateConfig.chatTemplate?.stringValue {
Copy link
Member

Choose a reason for hiding this comment

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

Technically, this could potentially be an array too. But this is discouraged.

@DePasqualeOrg
Copy link
Contributor Author

Thanks for your suggestions, @pcuenca. I made some changes based on your input.

@pcuenca pcuenca merged commit be855fa into huggingface:main Feb 27, 2025
1 check passed
@DePasqualeOrg
Copy link
Contributor Author

We'll need a new version tag in this repo to pick this up in mlx-swift-examples so that we can make Qwen 2.5 VL work with the correct chat template.

@pcuenca
Copy link
Member

pcuenca commented Mar 3, 2025

Added

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