{"id":737336,"date":"2026-09-04T04:06:58","date_gmt":"2026-09-04T03:06:58","guid":{"rendered":"https:\/\/blog.jetbrains.com\/?post_type=junie&#038;p=737336"},"modified":"2026-09-04T04:07:02","modified_gmt":"2026-09-04T03:07:02","slug":"so-haben-wir-das-modell-qwen-3-6-fuer-unseren-agent-junie-optimiert","status":"publish","type":"junie","link":"https:\/\/blog.jetbrains.com\/de\/junie\/2026\/09\/so-haben-wir-das-modell-qwen-3-6-fuer-unseren-agent-junie-optimiert\/","title":{"rendered":"So haben wir das Modell Qwen\u00a03.6 f\u00fcr unseren Agent Junie optimiert"},"content":{"rendered":"<p>Vor einiger Zeit haben wir ein langfristiges Projekt gestartet, um auf unterschiedlichen Hardwarekonfigurationen die vollst\u00e4ndig lokale Ausf\u00fchrung von Junie \u2013 einschlie\u00dflich lokaler Inferenz \u2013<s> <\/s>zu erm\u00f6glichen. Nach langer Vorarbeit haben wir <a href=\"https:\/\/blog.jetbrains.com\/junie\/2026\/08\/junie-local-launch\/\">vor Kurzem eine erste Version von Junie Local ver\u00f6ffentlicht<\/a>, die auf MacBooks mit M5-Chip Qwen3.6-27B ausf\u00fchrt.<\/p>\n<p>In diesem Blogartikel m\u00f6chte ich erkl\u00e4ren, wie wir das erreicht haben und warum wir uns f\u00fcr Qwen3.6-27B statt Qwen3.8-27B entschieden haben. Wir haben Optimierungen im gesamten Stack vorgenommen \u2013 vom Junie-Agent selbst bis hin zur verwendeten Inferenz-Engine.<\/p>\n<div class=\"buttons\">\n<div class=\"buttons__row\"><a class=\"btn\" href=\"https:\/\/jb.gg\/jlblog\" target=\"_blank\" rel=\"noopener\">Junie Local installieren<\/a><\/div>\n<\/div>\n<p>Beginnen wir mit Junie, denn hier beginnt die Interaktion mit dem lokalen Modell.<\/p>\n<h1 class=\"wp-block-heading\">Optimierungen an Junie<\/h1>\n<h2 class=\"wp-block-heading\">Erweiterung des rollierenden Agent-Kontexts<\/h2>\n<p>Wie jeder andere Coding-Agent besitzt auch Junie eine zentrale Ausf\u00fchrungsschleife, in der die gesamte Arbeit erledigt wird:<\/p>\n<ul class=\"wp-block-list\">\n<li>Im ersten Schritt gibt die Benutzerin oder der Benutzer eine Aufgabe vor.<\/li>\n<li>Junie \u00fcbermittelt diese Aufgabe an das LLM.<\/li>\n<li>Das LLM antwortet mit Toolaufrufen (z.\u00a0B. Bash-Befehle, Anweisungen zum Lesen\/Schreiben von Dateien und weitere Aktionen).<\/li>\n<li>Schlie\u00dflich sendet Junie das Ergebnis zur\u00fcck.<\/li>\n<\/ul>\n<p>Hier ist ein stark vereinfachtes Ablaufdiagramm der Vorg\u00e4nge, die im Hintergrund ausgef\u00fchrt werden:<\/p>\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" class=\"wp-image-733129\" src=\"https:\/\/blog.jetbrains.com\/wp-content\/uploads\/2026\/08\/scheme_1_light.png\" alt=\"\" width=\"901\" height=\"564\" \/><\/figure>\n<p>Wie das Diagramm zeigt, erh\u00e4lt das LLM fortlaufend Anfragen zur Kontexterweiterung, wobei Informationen aus bereits verarbeiteten Anfragen teilweise wiederverwendet werden. Genauer ausgedr\u00fcckt k\u00f6nnen wir die Prefill-Daten der vorherigen Anfrage f\u00fcr die n\u00e4chste wiederverwenden, und diese Daten werden als KV-Cache bezeichnet.<\/p>\n<p>Wenn wir Junie jedoch in derselben Sitzung mit einer zweiten Aufgabe betrauen, \u00fcbernimmt das System nur relevante Teile aus dem Kontext in das Kontextfenster:<\/p>\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" class=\"wp-image-733141\" src=\"https:\/\/blog.jetbrains.com\/wp-content\/uploads\/2026\/08\/scheme_2_light.png\" alt=\"\" width=\"901\" height=\"560\" \/><\/figure>\n<p>Bei Cloud-Modellen ist dies in der Regel kein Problem, da das Modell den Inhalt von Dateien bei erneutem Bedarf einfach neu anfordert und wieder einliest. Zudem ist das Prefill extrem schnell.<\/p>\n<p>F\u00fcr lokale Modelle gilt das nicht \u2013 dort erfolgt das Prefill langsamer und das \u201eLesen\u201c von Dateien kostet betr\u00e4chtliche Zeit.<\/p>\n<p>Um dieses Problem zu l\u00f6sen, haben wir die Logik f\u00fcr die lokale Inferenz angepasst. Jede neue Anfrage wird direkt dem rollierenden Kontext hinzugef\u00fcgt:<\/p>\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" class=\"wp-image-733153\" src=\"https:\/\/blog.jetbrains.com\/wp-content\/uploads\/2026\/08\/scheme_3_light.png\" alt=\"\" width=\"901\" height=\"772\" \/><\/figure>\n<p>Auf diese Weise k\u00f6nnen wir KV-Caches aus der vorherigen Aufgabe wiederverwenden, d.\u00a0h. wenn das Modell eine Datei bereits gelesen hat, verbleibt sie im Kontextfenster und muss nicht erneut gelesen werden.<\/p>\n<h2 class=\"wp-block-heading\">Maximale Wiederverwendung des anf\u00e4nglichen Pr\u00e4fixes<\/h2>\n<p>Eine weitere \u00e4hnliche Optimierung betrifft den System-Prompt und den initialen Kontext, den Junie zu Beginn einer neuen Coding-Sitzung \u00fcbermittelt.<\/p>\n<p>Im vorherigen Abschnitt war der Ablauf etwas vereinfacht dargestellt: Bei der ersten LLM-Anfrage werden tats\u00e4chlich deutlich mehr Daten an das LLM gesendet als im Diagramm dargestellt:<\/p>\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" class=\"wp-image-733164\" src=\"https:\/\/blog.jetbrains.com\/wp-content\/uploads\/2026\/08\/scheme_4_light.png\" alt=\"\" width=\"901\" height=\"592\" \/><\/figure>\n<p>Wie Sie sehen, werden noch ganz andere Informationen an das LLM \u00fcbermittelt. All diese Informationen werden zu Beginn jeder neuen Sitzung \u00fcbertragen und verarbeitet. Nat\u00fcrlich w\u00e4re es super, wenn wir auch diese Daten zwischenspeichern k\u00f6nnten \ud83d\ude42<\/p>\n<p>Daher haben wir die Reihenfolge ge\u00e4ndert, in der wir diese Daten \u00fcbertragen:<\/p>\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" class=\"wp-image-733175\" src=\"https:\/\/blog.jetbrains.com\/wp-content\/uploads\/2026\/08\/scheme_5_light.png\" alt=\"\" width=\"901\" height=\"592\" \/><\/figure>\n<p>Au\u00dferdem haben wir die Inferenz-Engine mit einer speziellen Logik ausgestattet, um den Pr\u00e4fix bis hin zur Benutzeranfrage im Cache zu halten, sodass er bei nachfolgenden Aufgaben (im selben Projekt) einfach wiederverwendet wird. Den Projektkontext nach der Benutzeranfrage haben wir ausgespart, da er recht klein ist, \u00fcberwiegend aus Dateien der obersten Projektebene besteht und sich daher h\u00e4ufig \u00e4ndern kann.<\/p>\n<h2 class=\"wp-block-heading\">Umsetzung von Fortschrittsmeldungen<\/h2>\n<p>Bei leistungsf\u00e4higeren Cloud-Modellen fordert Junie das LLM auf, Fortschrittsmeldungen f\u00fcr Benutzer*innen in einem XML-\u00e4hnlichen Block auszugeben. Leider werden solche Aufforderungen von Qwen 3.6 gr\u00f6\u00dftenteils ignoriert. Gleichzeitig beschreibt das Modell seine Aktionen als Klartext innerhalb der LLM-Antwort \u2013 d.\u00a0h. das LLM sendet Toolaufrufe zusammen mit einem erkl\u00e4renden Begleittext.<\/p>\n<p>Die L\u00f6sung ist simpel: Wir nutzen einfach diesen von Qwen 3.6 erzeugten Text als Fortschrittsinformationen f\u00fcr Benutzer*innen. Solche Anpassungen sind modellspezifisch. In unserem Fall hatten wir mit Qwen 3.6 Gl\u00fcck \u2013 manche Modelle geben an dieser Stelle \u00fcberhaupt keinen Text aus, w\u00e4hrend andere viel zu ausf\u00fchrlich sind.<\/p>\n<h2 class=\"wp-block-heading\">Vermeidung \u00fcberfl\u00fcssiger LLM-Aufrufe<\/h2>\n<p>Die n\u00e4chste Optimierung \u2013 das Deaktivieren aller optionalen LLM-Anfragen \u2013 mag trivial erscheinen, hat den Agent aber deutlich effizienter gemacht. Konkret bedeutete dies, dass wir jegliche Logik zur Erstellung einer kurzen Aufgabenzusammenfassung deaktiviert haben. Damit ging zwar ein kleiner Teil der Nutzungserfahrung verloren, doch dies f\u00e4llt nach unserer Einsch\u00e4tzung kaum ins Gewicht. Au\u00dferdem schalteten wir den Multi-Agent-Modus vollst\u00e4ndig ab, da die sequenzielle Verarbeitung von LLM-Anfragen auf einem M5 am effektivsten ist; mehrere Agents zu nutzen ist zwecklos, da die Inferenz ohnehin den Flaschenhals bildet.<\/p>\n<h1 class=\"wp-block-heading\">Optimierung der Modellparameter<\/h1>\n<h2 class=\"wp-block-heading\">reasoning_effort: none<\/h2>\n<p>Bei internen Tests der Cloud-Version von Qwen3.6-27B stellten wir fest, dass die Aktivierung von Reasoning keinen nennenswerten Qualit\u00e4tsgewinn brachte. Aus diesem Grund haben wir das Reasoning in der lokalen Version komplett abgeschaltet. Das macht einen gro\u00dfen Unterschied, denn aus Sicht der Inferenz-Engine gibt es keinen Unterschied zwischen den Reasoning-Token und den Token der eigentlichen Antwort. Ohne Reasoning fallen daher 2- bis 3-mal weniger Token an, was die Aufgabenausf\u00fchrung bei vernachl\u00e4ssigbarem Qualit\u00e4tsverlust etwa doppelt so schnell macht.<\/p>\n<h2 class=\"wp-block-heading\">Quantisierung<\/h2>\n<p>Wir haben uns f\u00fcr die 4-Bit-Version entschieden, da sie in Benchmarks nur minimal hinter der 8-Bit-Variante zur\u00fcckbleibt \u2013 und da die Generierung speicherlimitiert ist, l\u00e4uft die 4-Bit-Version rund doppelt so schnell wie die 8-Bit-Version. Als wir jedoch die Prefill-Geschwindigkeit der 8-Bit- und 4-Bit-Versionen verglichen, stellten wir fest, dass beide gleich schnell waren. Das kam uns merkw\u00fcrdig vor, also sind wir der Sache auf den Grund gegangen.<\/p>\n<h1 class=\"wp-block-heading\">Optimierung der Inferenz-Engine<\/h1>\n<h2 class=\"wp-block-heading\">Der Prefill-Hack<\/h2>\n<p>Vielleicht wundern Sie sich, warum wir dem Prefill \u00fcberhaupt so viel Beachtung schenken. Im Netz dreht sich schlie\u00dflich fast alles um Benchmarks der Generierungsgeschwindigkeit und M\u00f6glichkeiten zu deren Optimierung.<\/p>\n<p>Bei einer dedizierten GPU wie der RTX 5090 mag dieser Einwand tats\u00e4chlich berechtigt sein. Auf solcher Hardware l\u00e4uft das Prefill in der Tat extrem schnell, da es rechenlimitiert ist und dedizierte GPUs meist \u00fcber relativ viel Rechenleistung verf\u00fcgen. Damit lassen sich bei Standardkonfigurationen Prefill-Geschwindigkeiten von rund 3.700 Token\/s erzielen. Beim M5 lag dieser Wert anfangs bei etwa 650 Token\/s. Fragte das Modell bei Analyseaufgaben Dateiinhalte ab, entfiel somit der Gro\u00dfteil der Zeit auf das Prefill und nicht auf die Generierung!<\/p>\n<p>Erschwerend kam hinzu, dass es bei der Prefill-Geschwindigkeit keinerlei Unterschied zwischen 4-Bit-, 8-Bit- und 16-Bit-Quantisierung gab. Aber woran lag das? Nun: Da das Prefill rechenlimitiert ist, stellt die Speichergeschwindigkeit \u00fcberhaupt keinen Engpass dar. Und wie sich herausstellte, wurde der Gro\u00dfteil der Matrixoperationen beim Prefill mit voller 16-Bit-Pr\u00e4zision ausgef\u00fchrt. Alle 4-Bit-Gewichte wurden also vor der Berechnung in 16-Bit-Werte konvertiert. Der M5-Prozessor verf\u00fcgt jedoch \u00fcber spezielle Befehle f\u00fcr 8-Bit-Zahlen, die wesentlich schneller als 16-Bit-Operationen ablaufen. Nachdem wir mit einem Patch im MLX-VLM-Paket einige<sup>*<\/sup> Matrixberechnungen beim Prefill auf 8-Bit umgestellt hatten, stieg die Prefill-Geschwindigkeit um rund 40%!<\/p>\n<p>Dies ist \u00fcbrigens der Hauptgrund, warum wir uns auf M5-Chips konzentriert haben. Auf M4-Chips fehlen diese 8-Bit-Rechenbefehle, weshalb das Prefill mit 16-Bit-Arithmetik um 20 bis 30% langsamer erfolgt.<\/p>\n<p class=\"has-text-align-left\"><sup>*<\/sup>Qwen3.6-27B nutzt sowohl Full-Attention- als auch Self-Attention-Schichten. Wir stellten fest, dass die Full-Attention-Gewichte selbst bei einer 4-Bit-Quantisierung in voller 16-Bit-Pr\u00e4zision vorlagen. Da diese Schichten ohnehin mit voller Pr\u00e4zision arbeiten, haben wir die Optimierung dort nicht angewendet \u2013 sie greift nur bei den Self-Attention-Schichten, wo sie den Speicher- und Rechenaufwand tats\u00e4chlich reduziert. Hier ist der <a href=\"https:\/\/github.com\/JetBrains\/mlx-vlm\/tree\/feature\/int8-prefill\/research\" target=\"_blank\" rel=\"noopener\">Link<\/a> zum MLX-VLM-Patch. Dieselbe Optimierung l\u00e4sst sich sogar in vLLM vornehmen, indem einfach die Konfigurationsdatei des Modells angepasst wird. <a href=\"https:\/\/github.com\/erokhins\/vllm-qwen3.6-27b-nvfp4\/commit\/b85e97d316ee16590c65c4739f70ed7eaeab3025\" target=\"_blank\" rel=\"noopener\">Konfigurationsbeispiel<\/a><\/p>\n<h2 class=\"wp-block-heading\">Spekulative Dekodierung mittels MTP und N-Gram-Abgleich<\/h2>\n<p>Wir haben folgende Standardoptimierungen angewendet:<\/p>\n<ul class=\"wp-block-list\">\n<li><strong>MTP (Multi-Token-Vorhersage) mit separatem Entwurfsmodell<\/strong>: Ein Verfahren zur spekulativen Dekodierung, bei dem ein kleineres Entwurfsmodell die n\u00e4chsten Token vorschl\u00e4gt, die vom Hauptmodell nur noch validiert werden.<\/li>\n<li><strong>Spekulative N-Gram-Dekodierung:<\/strong> Anstelle eines Entwurfsmodells sucht diese Methode nach bereits wiederholten Token-Sequenzen im Kontext und \u201eprognostiziert\u201c nachfolgende Token durch einen Abgleich mit diesen Mustern.<\/li>\n<\/ul>\n<p>Wir haben beide Methoden parallel aktiviert. In der Praxis bedeutet dies, dass w\u00e4hrend der Generierung gelegentlich nicht nur die rund 3 \u00fcber MTP vorgeschlagenen Token \u00fcbernommen werden, sondern zus\u00e4tzlich bis zu 8 weitere Token aus der N-Gram-Methode. Die nachstehende Abbildung zeigt die generierten Raw-Token, farblich gekennzeichnet nach der Herkunft des jeweiligen Tokens (<mark class=\"has-inline-color has-vivid-green-cyan-color\">Entwurfsmodell<\/mark> bzw. <mark class=\"has-inline-color has-pale-cyan-blue-color\">N-Gram<\/mark>).<\/p>\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" class=\"wp-image-733304\" src=\"https:\/\/blog.jetbrains.com\/wp-content\/uploads\/2026\/08\/image-63-3.png\" alt=\"\" width=\"1898\" height=\"1050\" \/><\/figure>\n<p>Durch diese Kombination kann die Generierung bis zu doppelt so schnell erfolgen.<\/p>\n<h1 class=\"wp-block-heading\">Qwen3.8-27b<\/h1>\n<p>Weshalb fiel unsere Wahl trotz alledem nicht auf 3.8 anstelle von 3.6?<\/p>\n<p>Leider liefert Qwen 3.8 nur bei aktiviertem Reasoning gute Ergebnisse. Ohne Reasoning sinkt die Ausgabequalit\u00e4t erheblich \u2013 bei typischen Aufgaben kann das Modell sogar komplett versagen und in einer Endlosschleife denselben Toolaufruf wiederholen. Die Aktivierung des Reasoning erh\u00f6ht jedoch die Anzahl der generierten Token erheblich: Bei \u201eMedium\u201c-Reasoning werden etwa 5-mal mehr Token produziert. Da die Prefill-Zeit ann\u00e4hernd gleich bleibt, wird der gesamte Vorgang eher um den Faktor 4 als um den vollen Faktor 5 langsamer. Dieser Performanceverlust ist immer noch gravierend, sodass Qwen3.6-27B auf Mac-Hardware vorerst die bessere Wahl bleibt.<\/p>\n<h2 class=\"wp-block-heading\">Fazit<\/h2>\n<p>Dieser Erfahrungsbericht zeigt hoffentlich, dass es bei typischen agentischen Programmieraufgaben nicht ausreicht, blo\u00df auf die Geschwindigkeit der Tokengenerierung zu achten.<\/p>\n<p>Vielmehr m\u00fcssen alle Ebenen des Stacks gleicherma\u00dfen optimiert werden:<\/p>\n<ul class=\"wp-block-list\">\n<li><strong>Generierung und Prefill<\/strong>: Der schrittweise Token-Dekodierungsprozess und der erste Kontextverarbeitungslauf.<\/li>\n<li><strong>Modellparameter und Quantisierung<\/strong>: Die Pr\u00e4zision der Gewichte sowie die Konfiguration des Modells.<\/li>\n<li><strong>Agent-Harness<\/strong>: Die \u00fcbergeordnete Orchestrierungsebene (Toolaufrufe, Ablaufsteuerung, Prompt-Logik), die das Modell steuert.<\/li>\n<\/ul>\n<p>In diese Richtung m\u00f6chten wir in Zukunft weiterarbeiten. Die M5-Unterst\u00fctzung war erst der Anfang: Wir haben bereits Prototypen f\u00fcr DGX Spark und RTX 5090 (und evaluieren sogar 24-GB-Grafikkarten). Bleiben Sie gespannt!<\/p>\n<div class=\"buttons\">\n<div class=\"buttons__row\"><a class=\"btn\" href=\"https:\/\/jb.gg\/jlblog\" target=\"_blank\" rel=\"noopener\">Junie Local ausprobieren<\/a><\/div>\n<\/div>\n<p>\u00a0<\/p>\n<p><strong>Autor des urspr\u00fcnglichen Blogposts<\/strong><\/p>\n\n    <div class=\"about-author \">\n        <div class=\"about-author__box\">\n            <div class=\"row\">\n                <div class=\"about-author__box-img\">\n                    <img decoding=\"async\" src=\"https:\/\/secure.gravatar.com\/avatar\/a9f638d3eb6cd2a4afd3a0ee2b6e72bd?s=200&#038;r=g\" width=\"200\" height=\"200\" alt=\"Stanislav Erokhin\" loading=\"lazy\"  class=\"avatar avatar-200 wp-user-avatar wp-user-avatar-200 photo avatar-default\">\n                <\/div>\n                <div class=\"about-author__box-text\">\n                                            <h4>Stanislav Erokhin<\/h4>\n                                                        <\/div>\n            <\/div>\n        <\/div>\n    <\/div>\n","protected":false},"author":964,"featured_media":737337,"comment_status":"closed","ping_status":"closed","template":"","categories":[9159],"tags":[8759,9232,477],"cross-post-tag":[],"class_list":["post-737336","junie","type-junie","status-publish","has-post-thumbnail","hentry","category-agentic-ai","tag-junie","tag-local-ai","tag-release"],"acf":[],"_links":{"self":[{"href":"https:\/\/blog.jetbrains.com\/de\/wp-json\/wp\/v2\/junie\/737336","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/blog.jetbrains.com\/de\/wp-json\/wp\/v2\/junie"}],"about":[{"href":"https:\/\/blog.jetbrains.com\/de\/wp-json\/wp\/v2\/types\/junie"}],"author":[{"embeddable":true,"href":"https:\/\/blog.jetbrains.com\/de\/wp-json\/wp\/v2\/users\/964"}],"replies":[{"embeddable":true,"href":"https:\/\/blog.jetbrains.com\/de\/wp-json\/wp\/v2\/comments?post=737336"}],"version-history":[{"count":2,"href":"https:\/\/blog.jetbrains.com\/de\/wp-json\/wp\/v2\/junie\/737336\/revisions"}],"predecessor-version":[{"id":737360,"href":"https:\/\/blog.jetbrains.com\/de\/wp-json\/wp\/v2\/junie\/737336\/revisions\/737360"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/blog.jetbrains.com\/de\/wp-json\/wp\/v2\/media\/737337"}],"wp:attachment":[{"href":"https:\/\/blog.jetbrains.com\/de\/wp-json\/wp\/v2\/media?parent=737336"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blog.jetbrains.com\/de\/wp-json\/wp\/v2\/categories?post=737336"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blog.jetbrains.com\/de\/wp-json\/wp\/v2\/tags?post=737336"},{"taxonomy":"cross-post-tag","embeddable":true,"href":"https:\/\/blog.jetbrains.com\/de\/wp-json\/wp\/v2\/cross-post-tag?post=737336"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}