Merge branch 'main' into fix/transformers-v5-tiers

This commit is contained in:
Roland Tannous 2026-04-06 23:15:16 +04:00 committed by GitHub
commit b03e07ee2d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 93 additions and 51 deletions

View file

@ -819,7 +819,7 @@ shell.Run cmd, 0, False
if ($SkipTorch) {
# No-torch: install unsloth + unsloth-zoo with --no-deps, then
# runtime deps (typer, safetensors, transformers, etc.) with --no-deps.
$baseInstallExit = Invoke-InstallCommand { uv pip install --python $VenvPython --no-deps --reinstall-package unsloth --reinstall-package unsloth-zoo "unsloth>=2026.4.2" unsloth-zoo }
$baseInstallExit = Invoke-InstallCommand { uv pip install --python $VenvPython --no-deps --reinstall-package unsloth --reinstall-package unsloth-zoo "unsloth>=2026.4.4" unsloth-zoo }
if ($baseInstallExit -eq 0) {
$NoTorchReq = Find-NoTorchRuntimeFile
if ($NoTorchReq) {
@ -827,7 +827,7 @@ shell.Run cmd, 0, False
}
}
} else {
$baseInstallExit = Invoke-InstallCommand { uv pip install --python $VenvPython --reinstall-package unsloth --reinstall-package unsloth-zoo "unsloth>=2026.4.2" unsloth-zoo }
$baseInstallExit = Invoke-InstallCommand { uv pip install --python $VenvPython --reinstall-package unsloth --reinstall-package unsloth-zoo "unsloth>=2026.4.4" unsloth-zoo }
}
if ($baseInstallExit -ne 0) {
Write-Host "[ERROR] Failed to install unsloth (exit code $baseInstallExit)" -ForegroundColor Red
@ -857,7 +857,7 @@ shell.Run cmd, 0, False
if ($SkipTorch) {
# No-torch: install unsloth + unsloth-zoo with --no-deps, then
# runtime deps (typer, safetensors, transformers, etc.) with --no-deps.
$baseInstallExit = Invoke-InstallCommand { uv pip install --python $VenvPython --no-deps --upgrade-package unsloth --upgrade-package unsloth-zoo "unsloth>=2026.4.2" unsloth-zoo }
$baseInstallExit = Invoke-InstallCommand { uv pip install --python $VenvPython --no-deps --upgrade-package unsloth --upgrade-package unsloth-zoo "unsloth>=2026.4.4" unsloth-zoo }
if ($baseInstallExit -eq 0) {
$NoTorchReq = Find-NoTorchRuntimeFile
if ($NoTorchReq) {
@ -865,7 +865,7 @@ shell.Run cmd, 0, False
}
}
} elseif ($StudioLocalInstall) {
$baseInstallExit = Invoke-InstallCommand { uv pip install --python $VenvPython --upgrade-package unsloth "unsloth>=2026.4.2" unsloth-zoo }
$baseInstallExit = Invoke-InstallCommand { uv pip install --python $VenvPython --upgrade-package unsloth "unsloth>=2026.4.4" unsloth-zoo }
} else {
$baseInstallExit = Invoke-InstallCommand { uv pip install --python $VenvPython --upgrade-package unsloth "$PackageName" }
}
@ -886,7 +886,7 @@ shell.Run cmd, 0, False
# Fallback: GPU detection failed to produce a URL -- let uv resolve torch
substep "installing unsloth (this may take a few minutes)..."
if ($StudioLocalInstall) {
$baseInstallExit = Invoke-InstallCommand { uv pip install --python $VenvPython unsloth-zoo "unsloth>=2026.4.2" --torch-backend=auto }
$baseInstallExit = Invoke-InstallCommand { uv pip install --python $VenvPython unsloth-zoo "unsloth>=2026.4.4" --torch-backend=auto }
if ($baseInstallExit -ne 0) {
Write-Host "[ERROR] Failed to install unsloth (exit code $baseInstallExit)" -ForegroundColor Red
return

View file

@ -1040,7 +1040,7 @@ if [ "$_MIGRATED" = true ]; then
# to prevent transitive torch resolution.
run_install_cmd "install unsloth (migrated no-torch)" uv pip install --python "$_VENV_PY" --no-deps \
--reinstall-package unsloth --reinstall-package unsloth-zoo \
"unsloth>=2026.4.2" unsloth-zoo
"unsloth>=2026.4.4" unsloth-zoo
_NO_TORCH_RT="$(_find_no_torch_runtime)"
if [ -n "$_NO_TORCH_RT" ]; then
run_install_cmd "install no-torch runtime deps" uv pip install --python "$_VENV_PY" --no-deps -r "$_NO_TORCH_RT"
@ -1048,7 +1048,7 @@ if [ "$_MIGRATED" = true ]; then
else
run_install_cmd "install unsloth (migrated)" uv pip install --python "$_VENV_PY" \
--reinstall-package unsloth --reinstall-package unsloth-zoo \
"unsloth>=2026.4.2" unsloth-zoo
"unsloth>=2026.4.4" unsloth-zoo
fi
if [ "$STUDIO_LOCAL_INSTALL" = true ]; then
substep "overlaying local repo (editable)..."
@ -1070,7 +1070,7 @@ elif [ -n "$TORCH_INDEX_URL" ]; then
# runtime deps (typer, safetensors, transformers, etc.) with --no-deps.
run_install_cmd "install unsloth (no-torch)" uv pip install --python "$_VENV_PY" --no-deps \
--upgrade-package unsloth --upgrade-package unsloth-zoo \
"unsloth>=2026.4.2" unsloth-zoo
"unsloth>=2026.4.4" unsloth-zoo
_NO_TORCH_RT="$(_find_no_torch_runtime)"
if [ -n "$_NO_TORCH_RT" ]; then
run_install_cmd "install no-torch runtime deps" uv pip install --python "$_VENV_PY" --no-deps -r "$_NO_TORCH_RT"
@ -1081,7 +1081,7 @@ elif [ -n "$TORCH_INDEX_URL" ]; then
fi
elif [ "$STUDIO_LOCAL_INSTALL" = true ]; then
run_install_cmd "install unsloth (local)" uv pip install --python "$_VENV_PY" \
--upgrade-package unsloth "unsloth>=2026.4.2" unsloth-zoo
--upgrade-package unsloth "unsloth>=2026.4.4" unsloth-zoo
substep "overlaying local repo (editable)..."
run_install_cmd "overlay local repo" uv pip install --python "$_VENV_PY" -e "$_REPO_ROOT" --no-deps
else
@ -1092,7 +1092,7 @@ else
# Fallback: GPU detection failed to produce a URL -- let uv resolve torch
substep "installing unsloth (this may take a few minutes)..."
if [ "$STUDIO_LOCAL_INSTALL" = true ]; then
run_install_cmd "install unsloth (auto torch backend)" uv pip install --python "$_VENV_PY" unsloth-zoo "unsloth>=2026.4.2" --torch-backend=auto
run_install_cmd "install unsloth (auto torch backend)" uv pip install --python "$_VENV_PY" unsloth-zoo "unsloth>=2026.4.4" --torch-backend=auto
substep "overlaying local repo (editable)..."
run_install_cmd "overlay local repo" uv pip install --python "$_VENV_PY" -e "$_REPO_ROOT" --no-deps
else

View file

@ -88,7 +88,7 @@ huggingfacenotorch = [
]
huggingface = [
"unsloth[huggingfacenotorch]",
"unsloth_zoo>=2026.4.2",
"unsloth_zoo>=2026.4.3",
"torchvision",
"unsloth[triton]",
]
@ -578,7 +578,7 @@ colab-ampere-torch220 = [
"flash-attn>=2.6.3 ; ('linux' in sys_platform)",
]
colab-new = [
"unsloth_zoo>=2026.4.2",
"unsloth_zoo>=2026.4.3",
"packaging",
"tyro",
"transformers>=4.51.3,!=4.52.0,!=4.52.1,!=4.52.2,!=4.52.3,!=4.53.0,!=4.54.0,!=4.55.0,!=4.55.1,!=4.57.0,!=4.57.4,!=4.57.5,!=5.0.0,!=5.1.0,<=5.5.0",

View file

@ -225,6 +225,7 @@ const LoraCompareContent = memo(function LoraCompareContent({
modelType="base"
pairId={pairId}
initialThreadId={baseThreadId}
syncActiveThreadId={false}
>
<RegisterCompareHandle name="base" />
<Thread hideComposer={true} hideWelcome={true} />
@ -242,6 +243,7 @@ const LoraCompareContent = memo(function LoraCompareContent({
modelType="lora"
pairId={pairId}
initialThreadId={loraThreadId}
syncActiveThreadId={false}
>
<RegisterCompareHandle name="lora" />
<Thread hideComposer={true} hideWelcome={true} />
@ -343,6 +345,7 @@ const GeneralCompareContent = memo(function GeneralCompareContent({
modelType="model1"
pairId={pairId}
initialThreadId={model1ThreadId}
syncActiveThreadId={false}
>
<RegisterCompareHandle name="model1" />
<Thread hideComposer={true} hideWelcome={true} />
@ -376,6 +379,7 @@ const GeneralCompareContent = memo(function GeneralCompareContent({
modelType="model2"
pairId={pairId}
initialThreadId={model2ThreadId}
syncActiveThreadId={false}
>
<RegisterCompareHandle name="model2" />
<Thread hideComposer={true} hideWelcome={true} />
@ -479,11 +483,19 @@ function TopBarActions({
);
}
function getInitialSingleChatView(): ChatView {
const id = useChatRuntimeStore.getState().activeThreadId;
if (typeof id === "string" && id.length > 0 && !id.startsWith("__LOCALID_")) {
return { mode: "single", threadId: id };
}
return { mode: "single" };
}
export function ChatPage(): ReactElement {
const [view, setView] = useState<ChatView>({
mode: "single",
newThreadNonce: crypto.randomUUID(),
});
// Do not set newThreadNonce here: each /chat mount would run ThreadNewChatSwitch
// and create spurious threads when navigating (e.g. Recipes / Export). New Chat
// explicitly sets a nonce in handleNewThread.
const [view, setView] = useState<ChatView>(getInitialSingleChatView);
const [settingsOpen, setSettingsOpen] = useState(false);
const [modelSelectorOpen, setModelSelectorOpen] = useState(false);
const [modelSelectorLocked, setModelSelectorLocked] = useState(false);
@ -587,9 +599,20 @@ export function ChatPage(): ReactElement {
void ejectModel();
}, [ejectModel]);
const handleNewThread = useCallback(() => {
// Skip if we are already on a fresh unsaved draft with no messages sent.
// Once the user sends a message, append() sets activeThreadId in the store,
// so we check the store to know whether the current draft has been sent.
if (
view.mode === "single" &&
!view.threadId &&
!useChatRuntimeStore.getState().activeThreadId
) {
return;
}
useChatRuntimeStore.getState().setActiveThreadId(null);
setView({ mode: "single", newThreadNonce: crypto.randomUUID() });
}, []);
}, [view]);
const handleNewCompare = useCallback(() => {
setView({ mode: "compare", pairId: crypto.randomUUID() });
// Clear activeThreadId so compare panes do not inherit the single-chat
@ -922,7 +945,7 @@ export function ChatPage(): ReactElement {
{view.mode === "single" ? (
<SingleContent
key={view.threadId ?? view.newThreadNonce ?? "new"}
key={view.threadId ?? "single"}
threadId={view.threadId}
newThreadNonce={view.newThreadNonce}
/>

View file

@ -596,6 +596,15 @@ function ThreadHistoryProvider({
async append({ parentId, message }: ExportedMessageRepositoryItem) {
const { remoteId } = await aui.threadListItem().initialize();
// Keep single-chat runtime state in sync once a new chat is first
// persisted. Compare panes intentionally do not write global activeThreadId.
const thread = await db.threads.get(remoteId);
if (thread?.modelType === "base" && !thread.pairId) {
const store = useChatRuntimeStore.getState();
if (store.activeThreadId !== remoteId) {
store.setActiveThreadId(remoteId);
}
}
const content = cloneContent(message.content);
const attachments =
message.role === "user" ? cloneAttachments(message.attachments) : [];
@ -658,7 +667,11 @@ function useRuntimeHook(): ReturnType<typeof useLocalRuntime> {
function ThreadAutoSwitch({
threadId,
}: { threadId: string }): ReactElement | null {
syncActiveThreadId = true,
}: {
threadId: string;
syncActiveThreadId?: boolean;
}): ReactElement | null {
const aui = useAui();
const isLoading = useAuiState(({ threads }) => threads.isLoading);
const mainThreadId = useAuiState(({ threads }) => threads.mainThreadId);
@ -669,6 +682,13 @@ function ThreadAutoSwitch({
}
}, [aui, isLoading, mainThreadId, threadId]);
useEffect(() => {
if (!syncActiveThreadId || isLoading || mainThreadId !== threadId) {
return;
}
useChatRuntimeStore.getState().setActiveThreadId(threadId);
}, [isLoading, mainThreadId, syncActiveThreadId, threadId]);
return null;
}
@ -682,30 +702,10 @@ function ThreadNewChatSwitch({
if (isLoading) {
return;
}
let cancelled = false;
// Clear immediately so the adapter never picks up a stale thread ID
// from a previous chat while we initialize the new one.
// Switch to a fresh local thread without persisting it yet.
// Persistence still happens on first message append.
void aui.threads().switchToNewThread();
useChatRuntimeStore.getState().setActiveThreadId(null);
void (async () => {
try {
aui.threads().switchToNewThread();
const { remoteId } = await aui.threadListItem().initialize();
if (!cancelled) {
useChatRuntimeStore.getState().setActiveThreadId(remoteId);
}
} catch (error) {
if (!cancelled) {
useChatRuntimeStore.getState().setActiveThreadId(null);
}
console.error("Failed to initialize new chat thread", error);
}
})();
return () => {
cancelled = true;
};
}, [aui, isLoading, nonce]);
return null;
@ -733,12 +733,14 @@ export function ChatRuntimeProvider({
pairId,
initialThreadId,
newThreadNonce,
syncActiveThreadId = true,
}: {
children: ReactNode;
modelType?: ModelType;
pairId?: string;
initialThreadId?: string;
newThreadNonce?: string;
syncActiveThreadId?: boolean;
}): ReactElement {
const runtime = useRemoteThreadListRuntime({
runtimeHook: useRuntimeHook,
@ -754,8 +756,15 @@ export function ChatRuntimeProvider({
return (
<AssistantRuntimeProvider runtime={runtime} aui={aui}>
<ActiveThreadSync enabled={modelType === "base" && !pairId && !newThreadNonce} />
{initialThreadId && <ThreadAutoSwitch threadId={initialThreadId} />}
<ActiveThreadSync
enabled={modelType === "base" && !pairId && !newThreadNonce && !initialThreadId}
/>
{initialThreadId && (
<ThreadAutoSwitch
threadId={initialThreadId}
syncActiveThreadId={syncActiveThreadId}
/>
)}
{!initialThreadId && newThreadNonce && (
<ThreadNewChatSwitch nonce={newThreadNonce} />
)}

View file

@ -22,6 +22,7 @@ import {
} from "@hugeicons/core-free-icons";
import { HugeiconsIcon } from "@hugeicons/react";
import { db, useLiveQuery } from "./db";
import { useChatRuntimeStore } from "./stores/chat-runtime-store";
import type { ChatView, ThreadRecord } from "./types";
interface SidebarItem {
@ -76,12 +77,17 @@ export function ThreadSidebar({
onNewCompare: () => void;
showCompare: boolean;
}) {
const allThreads = useLiveQuery(
() => db.threads.orderBy("createdAt").reverse().toArray(),
[],
);
const allThreads = useLiveQuery(async () => {
const threadIdsWithMessage = new Set(
(await db.messages.orderBy("threadId").uniqueKeys()) as string[],
);
const rows = await db.threads.orderBy("createdAt").reverse().toArray();
return rows.filter((t) => !t.archived && threadIdsWithMessage.has(t.id));
}, []);
const items = groupThreads(allThreads ?? []);
const activeId = view.mode === "single" ? view.threadId : view.pairId;
const storeThreadId = useChatRuntimeStore((s) => s.activeThreadId);
const activeId =
view.mode === "single" ? (view.threadId ?? storeThreadId) : view.pairId;
function viewForItem(item: SidebarItem): ChatView {
return item.type === "single"
@ -101,7 +107,11 @@ export function ThreadSidebar({
}
}
if (activeId === item.id) {
onSelect({ mode: "single" });
// Directly set a new view with a nonce rather than going through
// onNewThread(), which may return early if the guard sees no
// threadId and no activeThreadId (after we just cleared it).
useChatRuntimeStore.getState().setActiveThreadId(null);
onSelect({ mode: "single", newThreadNonce: crypto.randomUUID() });
}
}

View file

@ -12,7 +12,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
__version__ = "2026.4.2"
__version__ = "2026.4.4"
__all__ = [
"SUPPORTS_BFLOAT16",