Skip to content

Comments

Make bitsandbytes optional on ROCm#474

Open
danielhanchen wants to merge 1 commit intomainfrom
fix/amd-optional-bnb
Open

Make bitsandbytes optional on ROCm#474
danielhanchen wants to merge 1 commit intomainfrom
fix/amd-optional-bnb

Conversation

@danielhanchen
Copy link
Contributor

This makes bitsandbytes an optional dependency so ROCm environments without bitsandbytes can still run Unsloth-Zoo workflows (16bit / bf16) end to end.

Changes:

  • patching_utils: guard patch_compiling_bitsandbytes when bitsandbytes is not installed
  • patching_utils: treat Linear4bit types as optional and avoid raising when bnb is missing
  • saving_utils: guard bitsandbytes import and isinstance checks
  • peft_utils: skip LoRA layer modules that fail to import (eg peft.tuners.lora.bnb) instead of crashing

This is NVIDIA-safe: when bitsandbytes is installed, behavior is unchanged.

@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello @danielhanchen, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly improves the compatibility of Unsloth-Zoo workflows, particularly for AMD ROCm environments, by making the bitsandbytes library an optional dependency. The changes ensure that the system can operate correctly with 16-bit and bf16 precision even when bitsandbytes is not installed, while maintaining full functionality for NVIDIA setups where it is present. This enhances the flexibility and reach of the Unsloth-Zoo framework without altering existing NVIDIA-specific behavior.

Highlights

  • Optional Bitsandbytes Dependency: The bitsandbytes library is now an optional dependency, allowing Unsloth-Zoo workflows to function in environments (like AMD ROCm) where it might not be installed, while maintaining full functionality for NVIDIA setups.
  • Conditional Patching: The patch_compiling_bitsandbytes function now gracefully skips its operations if bitsandbytes is not found, preventing crashes and ensuring compatibility.
  • Flexible Linear4bit Type Handling: Linear4bit types from bitsandbytes and peft are now treated as optional, avoiding ImportError exceptions and ensuring the code adapts when these modules are absent.
  • Robust LoRA Module Loading: The system for loading LoRA layer modules in peft_utils has been made more resilient, skipping modules that fail to import instead of crashing the application.
  • Guarded Saving Utilities: saving_utils now conditionally imports bitsandbytes and performs isinstance checks only when bitsandbytes is available, enhancing stability and preventing errors.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Changelog
  • unsloth_zoo/patching_utils.py
    • Wrapped the bitsandbytes import in patch_compiling_bitsandbytes with a try-except block, returning early if the import fails.
    • Modified the import of Linear4bit types from bitsandbytes.nn and peft.tuners.lora to handle ImportError by setting the respective type to None.
    • Updated the isinstance check for Bnb_Linear4bit and Peft_Linear4bit to first check if _bnb_types is not empty.
  • unsloth_zoo/peft_utils.py
    • Added try-except blocks around exec statements for importing peft.tuners.lora modules and their specific Linear classes, allowing the system to skip modules that fail to import.
  • unsloth_zoo/saving_utils.py
    • Encapsulated the bitsandbytes import in a try-except block, setting bnb to None if the import fails.
    • Added a conditional check (bnb is not None) before performing isinstance(module, bnb.nn.Linear4bit) to prevent errors when bitsandbytes is not installed.
Activity
  • No human activity has been recorded on this pull request yet.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b84f897dcc

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +149 to +152
try:
exec(f"import {item}", locals(), globals())
except Exception:
continue

Choose a reason for hiding this comment

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

P2 Badge Avoid silently skipping LoRA modules on unexpected import errors

Catching all exceptions here means any failure inside a LoRA module (e.g., a real bug or a missing dependency unrelated to bitsandbytes) is silently ignored, so its Linear* classes are never added to Linear_LoRA_Layers. In that scenario, models using that module type won’t be patched and LoRA layers may be missed without any warning, leading to incomplete training behavior that is hard to diagnose. If the intent is only to ignore optional backends, consider narrowing the exception to the specific missing dependency or at least logging unexpected import failures.

Useful? React with 👍 / 👎.

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request makes bitsandbytes an optional dependency, which is a great improvement for environments like ROCm that may not have it installed. The changes primarily involve adding try-except blocks to handle import errors gracefully. My review focuses on improving the robustness of this error handling by suggesting more specific exceptions (ImportError instead of Exception) to avoid masking other potential issues. Additionally, I've proposed a refactoring in peft_utils.py to replace the use of exec() with the safer importlib module for dynamic imports, which enhances code maintainability and security.

Comment on lines +149 to 160
try:
exec(f"import {item}", locals(), globals())
except Exception:
continue
modules = dir(eval(item))
modules = [x for x in modules if x.startswith("Linear") or x.endswith("Linear")]
if len(modules) == 0: continue
exec(f"from {item} import ({', '.join(modules)})", locals(), globals())
try:
exec(f"from {item} import ({', '.join(modules)})", locals(), globals())
except Exception:
continue
Linear_LoRA_Layers += [(eval(x), item, x,) for x in modules]
Copy link
Contributor

Choose a reason for hiding this comment

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

high

Using exec() and eval() for dynamic imports can introduce security vulnerabilities and make the code harder to debug and maintain. A safer and cleaner approach is to use the importlib module. This refactoring will make the code more robust and readable.

Please also add import importlib at the top of the file.

Suggested change
try:
exec(f"import {item}", locals(), globals())
except Exception:
continue
modules = dir(eval(item))
modules = [x for x in modules if x.startswith("Linear") or x.endswith("Linear")]
if len(modules) == 0: continue
exec(f"from {item} import ({', '.join(modules)})", locals(), globals())
try:
exec(f"from {item} import ({', '.join(modules)})", locals(), globals())
except Exception:
continue
Linear_LoRA_Layers += [(eval(x), item, x,) for x in modules]
try:
module_obj = importlib.import_module(item)
except ImportError:
# This can happen if bitsandbytes is not installed for modules like bnb.py
continue
module_names = [
x for x in dir(module_obj)
if (x.startswith("Linear") or x.endswith("Linear")) and isinstance(getattr(module_obj, x, None), type)
]
for module_name in module_names:
layer_class = getattr(module_obj, module_name)
Linear_LoRA_Layers.append((layer_class, item, module_name))

import bitsandbytes
try:
import bitsandbytes
except Exception:
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

It's a best practice to catch specific exceptions rather than the general Exception. In this case, an ImportError is expected if bitsandbytes is not installed. Using except ImportError: is more precise and prevents masking other potential errors during the import process.

Suggested change
except Exception:
except ImportError:

from bitsandbytes.nn import Linear4bit as Bnb_Linear4bit
except:
raise ImportError("Unsloth: Please install bitsandbytes via `pip install bitsandbytes`")
except Exception:
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

To make the code more robust, it's better to catch a more specific exception. Since you're handling a potentially missing optional dependency, ImportError is the more appropriate exception to catch here instead of the broad Exception.

Suggested change
except Exception:
except ImportError:

from peft.tuners.lora import Linear4bit as Peft_Linear4bit
except:
raise ImportError("Unsloth: Please install peft via `pip install peft`")
except Exception:
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

Similar to the previous comment, catching a specific ImportError is preferred over a general Exception. This ensures that you are only handling the case where the module is not found and not suppressing other unexpected errors.

Suggested change
except Exception:
except ImportError:

import bitsandbytes as bnb
try:
import bitsandbytes as bnb
except Exception:
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

It's better to catch specific exceptions. Here, ImportError is the expected exception if bitsandbytes is not installed. Using except ImportError: is safer as it won't hide other unexpected errors that might occur during the import.

Suggested change
except Exception:
except ImportError:

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.

1 participant