Anima 4-char LoRA: zeroing DiT-side conditioning swapped a girl across 3 seeds
Contents

Update (2026-08-09): Follow-up run testing whether a frozen Qwen3-0.6B plus a 50M resampler can rebuild the successful conditioning from short triggers → Anima 4-char conditioning distillation vs fixed-template P3 on M1 Max
In my previous run, where I stripped the 4-character prompt down in stages, trigger-only prompts (Lv0) produced every requested character in only 3 of 33 cases, while Lv3 prompts with ID anchors, position lines and outfit lines passed 8 of 11 combos across all 3 seeds.
With the same Anima-Base and the same 4-character LoRA, the four girls came out as four distinct characters only when the prompt spelled everything out at Lv3. Before swapping out the current Qwen3-0.6B, I wanted to know which path inside Anima decides whether that separation succeeds, given a short failing prompt (P0) and a long working one (P3).
Reading the implementation, Anima’s conditioning is not decided by Qwen alone. The LLMAdapter takes both the Qwen hidden state and the T5 token IDs and turns them into the conditioning. On top of observing and intervening on tensors, I also ran a cross experiment that feeds P0 and P3 into the Qwen side and the T5 side independently.
Anima’s conditioning is built from two paths, Qwen and T5
In the current ComfyUI implementation the prompt splits into two paths, Qwen3-0.6B-Base and the T5 tokenizer, and becomes a single conditioning after passing through the 6-layer LLMAdapter that ships inside Anima itself. To confirm the path I read the Anima tokenizer implementation and the LLMAdapter implementation in Anima itself at ComfyUI commit 0764232429b8cfb10b79b6f186c8cb23e0b22897, the one used during the experiment.
flowchart TD
P[Prompt] --> Q[Qwen3-0.6B<br/>last hidden state]
P --> T[T5 tokenizer<br/>token IDs and weights]
Q --> A[LLMAdapter<br/>6 transformer blocks]
T --> A
A --> C[conditioning<br/>1024-dim]
C --> D[Anima DiT<br/>cross-attention]
D --> I[generated image]
In the current code Qwen’s final hidden state is 1024-dim and the LLMAdapter assumes 1024 dims as well. The Qwen-side weight is fixed at 1.0, and the prompt weight is applied from the T5 side onto the LLMAdapter output.
Measured path and shapes
The runtime is a local ComfyUI. Looking at the workflow my generation server builds, the actual generation path is CLIPLoader (qwen_3_06b_base.safetensors, type qwen_image) → CLIPTextEncode → KSampler (with anima-base-v1.0.safetensors loaded through UNETLoader).
In the comfy implementation, the LLMAdapter lives on the DiT checkpoint side (anima-base-v1.0.safetensors) and is called from inside the DiT forward (Anima.preprocess_text_embeds in comfy/ldm/anima/model.py). What CLIPTextEncode returns is only Qwen3-0.6B’s last-layer hidden state plus the T5 token IDs and weights; T5-XXL itself (the 4.9GB t5xxl_fp8_e4m3fn.safetensors) is never loaded. The T5 tokenizer is used only to decide which IDs to look up in the LLMAdapter’s own trained embedding (nn.Embedding(32128, 1024)).
| Item | Measured value |
|---|---|
| ComfyUI commit | 0764232429b8cfb10b79b6f186c8cb23e0b22897 (2026-08-03). The most recent change to comfy/ldm/anima/model.py and friends is 57500fc5bc92566a63f2046824f522cd55c335ca (2026-08-02) |
| text encoder checkpoint | qwen_3_06b_base.safetensors (1.19GB, bf16) |
| DiT checkpoint | anima-base-v1.0.safetensors (4.18GB, bf16; 118 of its 685 tensors are net.llm_adapter.*) |
| Qwen input shape | (1, seq_len) int64. seq_len varies per prompt (38–184 measured, see below) |
| Qwen final hidden shape | (1, seq_len, 1024) float32. Padding positions are zero-cleared (Qwen3_06BModel in comfy/text_encoders/anima.py sets layer_norm_hidden_state=False) |
| Qwen layer count | 28 layers (measured from model.layers on Qwen3_06B → Llama2_) |
| LLMAdapter output shape | (1, t5_seq_len, 1024) bf16. The query-side sequence length is the T5 token count, not Qwen’s (the LLMAdapter uses T5 ids as the query and the Qwen hidden state as the cross-attention context) |
| DiT input conditioning shape | (1, 512, 1024). The LLMAdapter output is weighted by t5xxl_weights, then zero-padded up to 512 tokens (preprocess_text_embeds in comfy/ldm/anima/model.py) |
| Note | Confirmed bit-exact that adding hooks does not change the CLIPTextEncode output, by comparing the cond tensor with and without hooks |
Reproducing the failing P0 and the working P3 under the same conditions
Generation settings match the previous article. I first reproduced one pair of the earlier outputs and checked that the images did not change before and after inserting the observation code.
| Item | Fixed value |
|---|---|
| Base model | Anima-Base v1.0 |
| LoRA | anima-4char-v1_epoch100, rank256, strength 1.0 |
| Sampling | Turbo 8step / cfg 1.0 / er_sde / simple |
| seed | 42 / 1234 / 9999 |
| Resolution | 832×1216 (default_w/default_h of anima_base) |
| negative prompt | worst quality, low quality, blurry, jpeg artifacts, text, watermark, signature, multiple views, split view, cropped (same as the previous experiment) |
| Generation path | Through the generation server’s /api/generate (synchronous JSON API). ComfyUI itself is the already-running process |
Tensor extraction runs in a separate process (CPU-pinned, a different Python process from the ComfyUI server), so the state of the running ComfyUI server is never modified.
Reproduction results
Both were generated at seed 42.
| Condition | Previous verdict | Re-run verdict |
|---|---|---|
| 4 characters, Lv0 | × (0/3 across all seeds) | × (kanachan never appears; the two on the left are both kurara, one of them shorter) |
| 4 characters, Lv3 | ○ | ○ (all four keep the ID features they were trained with and stay distinct) |


Both Lv0 and Lv3 match the verdicts from the previous article. Judged by the same criterion, “does every requested character look like themselves”, Lv0 is × and Lv3 is ○, so I use these two images as the reference for the conditioning comparisons and interventions that follow.
Intermediate prompts between P0 and P3
I did not capture internal state for all 66 runs, only for the representative pairs where the previous results split into failure and success at the same seed.
| Series | Conditions compared | What I looked at |
|---|---|---|
| 4-character separation | 4 at Lv0 vs. 4 at Lv3 on the same seed | Whether all four come out as separate characters |
| 2-character separation | A pair that failed at Lv0 and came out right at Lv3 | Whether fewer characters break the same way |
| Weak attribute | koharu+keichan with and without the height line | At which stage a single sentence starts to matter |
Each series gets the following intermediate conditions as needed.
| Condition | Content | Purpose |
|---|---|---|
| P0 | Trigger words only | Failure baseline |
| P0-length | Pad to roughly P3’s length with scene description only, adding no per-character information | Isolate the effect of raw token count |
| P1-separate | Put each trigger in its own sentence, with no appearance attributes | Isolate the effect of sentence structure that separates characters |
| P2-anchor | Attach an ID anchor to each character | Look at attribute binding |
| P3 | The exact prompt that worked last time | Success baseline |
P0-length does not hold meaning constant, so it is not a condition that isolates length alone.
Prompts used for comparison
# 4 characters, P0 (quoted from the previous run)
masterpiece, best quality, safe, 4girls, multiple girls, kurara, keichan, kanachan, koharu, full body, white background, simple background
# 4 characters, P0-length (written for this experiment, length matching)
masterpiece, best quality, safe, 4girls, multiple girls, kurara, keichan, kanachan, koharu, full body, white background, simple background, standing together in a clean and bright studio with soft even lighting, a plain seamless backdrop, high resolution digital illustration with clean line art and detailed shading, four figures visible from head to toe in a single unbroken row
# 4 characters, P1-separate (written for this experiment)
masterpiece, best quality, safe, 4girls, multiple girls, full body, white background, simple background. kurara is present. keichan is present. kanachan is present. koharu is present.
# 4 characters, P2-anchor (quoted from the previous run)
masterpiece, best quality, safe, 4girls, multiple girls, kurara, keichan, kanachan, koharu, full body, white background, simple background. kurara has long rose-brown hair, stud earrings and light makeup. keichan is a blonde girl with a blue ribbon. kanachan has a brown side ponytail and an ahoge. koharu has short dark hair and red eyes.
# 4 characters, P3 (quoted from the previous run)
masterpiece, best quality, safe, 4girls, multiple girls, kurara, keichan, kanachan and koharu, full body, four girls standing in a row facing the viewer, white background, simple background. kurara, with long rose-brown hair, stud earrings and light makeup, is on the far left. keichan, a blonde girl with a blue ribbon, is second from the left, the same height as kurara. kanachan, with a brown side ponytail and an ahoge, is third from the left, half a head shorter. koharu, with short dark hair and red eyes, is on the far right, the shortest. everyone is wearing a plain white t-shirt and blue jeans.
# 4 characters, P3-order-reversed (written for this experiment, character order flipped)
masterpiece, best quality, safe, 4girls, multiple girls, koharu, kanachan, keichan and kurara, full body, four girls standing in a row facing the viewer, white background, simple background. koharu, with short dark hair and red eyes, is on the far left, the shortest. kanachan, with a brown side ponytail and an ahoge, is second from the left, half a head shorter than the tallest. keichan, a blonde girl with a blue ribbon, is third from the left. kurara, with long rose-brown hair, stud earrings and light makeup, is on the far right, the same height as keichan. everyone is wearing a plain white t-shirt and blue jeans.
# 2 characters, kurara+kanachan Lv0 (quoted from the previous run)
masterpiece, best quality, safe, 2girls, kurara, kanachan, full body, white background, simple background
# 2 characters, kurara+kanachan Lv3 (quoted from the previous run)
masterpiece, best quality, safe, 2girls, kurara and kanachan, full body, two girls standing side by side facing the viewer, white background, simple background. kurara, with long rose-brown hair, stud earrings and light makeup, is on the left. kanachan, with a brown side ponytail and an ahoge, is on the right, half a head shorter. everyone is wearing a plain white t-shirt and blue jeans.
# koharu+keichan without the height line (quoted from the previous run)
masterpiece, best quality, safe, 2girls, koharu and keichan, full body, two girls standing side by side facing the viewer, white background, simple background. koharu, with short dark hair and red eyes, is on the left. keichan, a blonde girl with a blue ribbon, is on the right. everyone is wearing a plain white t-shirt and blue jeans.
# koharu+keichan with the height line (quoted from the previous run, the version that passed all 3 seeds)
masterpiece, best quality, safe, 2girls, koharu and keichan, full body, two girls standing side by side facing the viewer, white background, simple background. koharu, a shorter girl with short dark hair and red eyes, is on the left. keichan, a blonde girl with a blue ribbon, is on the right. everyone is wearing a plain white t-shirt and blue jeans.
Token splits and truncation do not explain the failure
I ran the comparison prompts through both tokenizers and saved the token IDs, character positions in the original prompt, attention mask, weights, effective token count, how the trigger words split, and whether truncation happened at 512 tokens.
Qwen3-0.6B is decoder-only, so each token’s hidden state is determined only by the context before it. To check the effect of character order as well, I added a version of P3 with the character order reversed.
For the tokenizer I used the same AnimaTokenizer from comfy/text_encoders/anima.py that the generation server uses at generation time. The Qwen side is qwen25_tokenizer (Qwen2Tokenizer, has_start_token=False, has_end_token=False), the T5 side is t5_tokenizer (T5TokenizerFast).
Tokenization results
| prompt | Qwen tokens | T5 tokens | truncation |
|---|---|---|---|
| 4 characters, P0 | 38 | 43 | none |
| 4 characters, P0-length | 82 | 92 | none |
| 4 characters, P1-separate | 47 | 52 | none |
| 4 characters, P2-anchor | 91 | 108 | none |
| 4 characters, P3 | 155 | 179 | none |
| 4 characters, P3-order-reversed | 159 | 184 | none |
| 2 characters, kurara+kanachan Lv0 | 27 | 29 | none |
| 2 characters, kurara+kanachan Lv3 | 96 | 109 | none |
| koharu+keichan without the height line | 85 | 96 | none |
| koharu+keichan with the height line | 88 | 100 | none |
Every prompt sits far below 512 tokens, and truncation never happens. Short prompts do not fail because of truncation.
How many tokens a trigger word splits into is not the same across the four. kurara, keichan, kanachan and koharu are all invented words that are absent from the tokenizer vocabulary, so each is broken into a combination of existing subwords, and both the split points and the count differ per character.
| Trigger | Qwen split | T5 split |
|---|---|---|
| kurara | kur / ara (2 tokens) | kur / ara (2 tokens) |
| keichan | ke / ich / an (3 tokens) | ke / ich / an (3 tokens) |
| kanachan | kan / ach / an (3 tokens) | kan / a / chan (3 tokens, split at different points than Qwen) |
| koharu | k / oh / aru (3 tokens) | k / o / har / u (4 tokens, the finest split) |
Only kurara splits into 2 tokens; the other three split into 3 or 4. There is no simple correlation between how finely a trigger splits and how easily it breaks (koharu splits the finest, but failures collapse toward koharu far less often than toward kurara). Rather than split granularity, the higher occurrence count in the training data (312 for kurara) looks like the reason failures collapse toward kurara.
Comparing P3-order-reversed against P3, Qwen token count goes from 155 to 159 and T5 from 179 to 184, a slight increase. That is because the swap is not perfectly meaning-preserving: the height-gap phrasing was rewritten to match the new word order (“is second from the left, the same height as kurara” → “is third from the left”). This tokenization result alone cannot establish a causal relationship for character order.
The constraint that a token depends only on earlier context also shows up numerically in the tensor comparison below. As long as a trigger word sits near the front of the prompt, adding any amount of ID text after it barely changes the trigger word’s own Qwen hidden state.
Differences in token splits, truncation and masks do not explain what decides success and failure. Every prompt fits without truncation, and there is no simple correlation between split granularity and how easily a character breaks. Since nothing separates the conditions at the token stage, I moved on to comparing the conditioning downstream.
Cosine similarity on hidden states cannot measure whether the characters are drawn apart
From the same prompts I saved Qwen’s embedding output and hidden states from all layers, the output of each of the LLMAdapter’s 6 blocks, and the final conditioning just before it reaches the DiT. Everything is stored as float32 NPZ, with prompt, token mapping, shape, dtype and model hash recorded in a separate JSON.
Because short and long prompts have different sequence lengths, I did not compute cosine similarity or MSE over a flattened whole tensor. The unit of comparison is the part of the prompt that specifies one character. In P3 that is kanachan, with a brown side ponytail and an ahoge, is third from the left, half a head shorter., where the trigger word and the ID text that follows occupy a contiguous run of tokens. I call this the character’s description region from here on.
Here is what I compared.
- A vector pooled over the tokens of a character’s description region
- Cosine similarity between description regions within the same prompt
- Cross-layer comparison over only the tokens whose underlying string is unchanged
- How similar the per-character vectors are, before and after the LLMAdapter
Qwen-side and T5-side tokenization positions do not necessarily line up, so I never mixed the two token indices and instead mapped them separately through positions in the original string.
For the implementation I reproduced the CLIPTextEncode equivalent with comfy.sd.load_clip, and the LLMAdapter equivalent by pulling only the 118 net.llm_adapter.* tensors out of anima-base-v1.0.safetensors and running load_state_dict into comfy.ldm.anima.model.LLMAdapter, both with production weights (load confirmed with missing=[], unexpected=[]). The DiT itself (4.18GB) is not loaded. Both run CPU-pinned.
I put a forward hook on clip.encode_from_tokens and collected the output of all 28 Qwen layers and the 6 LLMAdapter blocks in one pass for comparison.
Tensor comparison
| prompt | Observation point | shape | Mean norm (over tokens) |
|---|---|---|---|
| 4 characters, P0 | Qwen final layer (all tokens) | (1, 38, 1024) | 110.67 |
| 4 characters, P0 | LLMAdapter output (all tokens, pre-padding, just before DiT) | (1, 43, 1024) | 5.59 |
| 4 characters, P3 | Qwen final layer (all tokens) | (1, 155, 1024) | 126.20 |
| 4 characters, P3 | LLMAdapter output (all tokens, pre-padding, just before DiT) | (1, 179, 1024) | 4.13 |
The Qwen final-layer norm goes up from P0 to P3, while the LLMAdapter output norm goes down. Since the conditioning that reaches the DiT is zero-padded out to 512 tokens, more effective tokens does not necessarily mean a larger share of tokens carrying information.
I tried two ways of pooling a character’s description region. Method 1 pools only the trigger word itself (first occurrence, 2–3 tokens). Method 2 pools from the trigger word to the end of that character’s ID text (up to whichever comes first, the next character’s trigger or a .) as one unit, excluding short back-reference re-occurrences and picking the occurrence that carries ID text.
With method 1 (trigger word only), kurara, keichan and kanachan produced exactly identical cosine values between P0 and P3.
| Pair | P0 (trigger only) | P3 (trigger only) |
|---|---|---|
| kurara-keichan | 0.8941 | 0.8941 (exact match) |
| kurara-kanachan | 0.8856 | 0.8856 (exact match) |
| keichan-kanachan | 0.9531 | 0.9531 (exact match) |
| kurara-koharu | 0.8501 | 0.8617 |
| keichan-koharu | 0.9087 | 0.9152 |
| kanachan-koharu | 0.9173 | 0.9212 |
P0 and P3 share the same leading string (up to kurara, keichan, kanachan), and differ only right before koharu, where P0 has , and P3 has and (because the Lv3 prompt writes kanachan and koharu). Since each token depends only on earlier context, kurara, keichan and kanachan, whose preceding context is literally identical in P0 and P3, produce exactly the same vectors, and only koharu shifts. That confirms by measurement that no amount of ID text after a trigger word meaningfully changes the trigger word’s own Qwen representation. The information that separates characters sits not in the trigger word itself but in the token positions carrying the ID text.
With method 2 (description regions including the ID text), the cosine similarity at Qwen’s final layer went up from P0 to P3 instead of down.
| prompt | Qwen final layer, mean cosine between character vectors | LLMAdapter output, mean cosine between character vectors |
|---|---|---|
| P0 (trigger only, no ID text) | 0.9332 | 0.3201 |
| P1-separate (triggers split into separate sentences) | 0.9528 | 0.4524 |
| P2-anchor (with ID anchors) | 0.9697 | 0.5002 |
| P3 (ID anchors + position lines + outfit line) | 0.9814 | 0.4805 |
At Qwen’s final layer, the more ID text you add, the more consistently the cosine between description regions rises (0.9332 at P0 → 0.9814 at P3). The LLMAdapter output rises from P0 to P2-anchor (0.3201 → 0.5002) but drops from P2-anchor to P3 (0.5002 → 0.4805). Overall the more you add, the more alike they look, which is the opposite of what I expected (“the longer the working prompt, the further apart the per-character vectors”).
A likely reason is that most ID sentences share the same grammatical template (“X has hair of some colour”, “X has eyes of some colour”), so with mean pooling the shared component of “a sentence describing a girl” comes out stronger than the individual differences in colour and shape. Plain mean pooling plus cosine gets dragged by that shared component and reports “the more ID text you write, the more alike the character vectors become”, the opposite of the actual image result (the more ID text you write, the better the characters are drawn apart). Cosine similarity is not evidence for whether the characters are being drawn apart.
Taking the cosine of trigger words alone across all 28 Qwen layers, the trend rises overall. Comparing P3’s trigger words (first occurrence only, no ID text) across all 28 layers, the value climbs from 0.59 at layer 0 to 0.94 at layer 27, with a dip in between from 0.83 at layer 14 to 0.77 at layer 20 (step2_layersweep.json). It is not monotonic, but the deeper layers may be feeling the anisotropy where every token points in a similar direction. This cosine alone cannot rule out the effect of a larger Qwen, but it is a reason to compare intermediate layers, not just the final one, if a larger Qwen gets tried.
The conditioning came out as numbers at both the Qwen final layer and the LLMAdapter output, but whether the cosine goes up or down as ID text is added does not match intuition, and tensor comparison alone could not tell me whether the characters were being drawn apart or not.
Zeroing the conditioning just before the DiT replaced one character
An attention map only shows where the model looks; it does not prove that the information changed the image. Keeping noise and seed fixed, I intervened on the character description regions.
- Replace one character’s description region with zeros
- Swap two characters’ description regions
- Intervene at the Qwen output and let everything from the LLMAdapter onward run as usual
- Perform the same intervention at the LLMAdapter output
- Never delete tokens; keep sequence length and positions and change only the values
If deleting kanachan’s description region makes only kanachan disappear, or only her appearance break, that is grounds for saying the region contributed to the image. If nothing changes, the mapping of the description region may simply be wrong.
For the implementation I called the node classes from nodes.py (UNETLoader, CLIPLoader, LoraLoader, CLIPTextEncode, KSampler, VAEDecode) directly, in the same order and with the same strengths as the generation server. I first generated P3 at seed 42 with the conditioning untouched and compared it against the reproduction image: composition, characters and clothing matched exactly (a mean absolute difference of 14.8/255 came from the --bf16-vae startup flag, but the generated content is identical). On top of that manual pipeline, I generated with only the positive conditioning swapped out.
There are four interventions, A through D, all on 4-character P3 at seed 42. A, B and C operate on description regions at Qwen’s final layer; only D operates at the LLMAdapter output (just before the DiT).
Here is the baseline with no intervention.

A, B and C all came out nearly identical to that baseline.
| Intervention | Operation | Change in the image |
|---|---|---|
| A | Zero kanachan’s description region | All four come out with the same characters and features as the baseline. kanachan keeps her side ponytail and ahoge |
| B | Zero koharu’s description region | All four come out with the same characters and features as the baseline. koharu keeps her short dark hair and red eyes |
| C | Swap kanachan’s and koharu’s description regions (exchanging mean-pooled values) | Nothing swaps; all four stay in the baseline arrangement |



The last one, intervention D, moves the operation from Qwen’s final layer to the LLMAdapter output and zeroes the same kanachan description region as A. This is the only one where the image clearly changed.
| Intervention | Operation | Change in the image |
|---|---|---|
| D | Zero kanachan’s description region at the LLMAdapter output (just before the DiT, pre-512-padding) | kanachan’s ID features (third from the left) disappear and she is replaced by a girl with nearly the same blonde hair and blue ribbon as keichan |

Only kanachan is replaced, and what disappears are the ID features: side ponytail, ahoge and brown hair. The other three stay at the baseline, and koharu (fourth) is unaffected.
I also computed the pixel difference against the baseline (mean absolute difference, 0–255).
| Intervention | Mean absolute difference (0-255) | Share of pixels changed by more than 10 | Visual verdict |
|---|---|---|---|
| A (Qwen side, kanachan zero) | 18.1 | 23.6% | Same characters, line-art level differences only |
| B (Qwen side, koharu zero) | 12.8 | 18.3% | Same characters, line-art level differences only |
| C (Qwen side, kanachan⇔koharu swap) | 19.2 | 26.1% | Same characters, no swap |
| D (LLMAdapter side, kanachan zero) | 32.5 | 39.0% | kanachan disappears, replaced by a keichan clone |
Zeroing a description region at Qwen’s final layer barely moves the image, while zeroing it at the LLMAdapter output does. This comparison alone, however, cannot separate whether the LLMAdapter reconstructed the character from the remaining Qwen tokens or from the T5 token IDs it receives as an independent input. That is what the cross experiment below adds.
Intervention D reproduced with the same breakage at seeds 1234 and 9999. In all three seeds, kanachan’s ID features (side ponytail, ahoge, brown hair) disappear and two girls with nearly the same blonde hair and blue ribbon as keichan stand side by side. Position (third) and clothing stay the same; only the ID features are replaced by keichan’s.


The replacement always landing on keichan was consistent too. The DiT-side fallback for a missing kanachan ID signal may be biased toward keichan. In the previous article most Lv0 failures involved kurara (she has the highest trigger occurrence count of the four in the training data), whereas this LLMAdapter-output intervention produced keichan’s features instead. Where the output collapses differs between the two runs.
Interventions A, B and C so far all shared one condition: only Qwen’s final hidden state was touched, while the T5 token IDs going into the LLMAdapter stayed as they were. That only shows that erasing the Qwen-side information alone does not change the image. It is not enough to say that the LLMAdapter is what decides the separation. The LLMAdapter receives the Qwen hidden state and the T5 token IDs as two independent inputs, and the T5 side turns trigger names and ID text into vectors directly through the LLMAdapter’s own trained embedding (nn.Embedding(32128, 1024)). Even with the Qwen side zeroed, kanachan’s name and ID text remain on the T5 side, and the image may be reconstructed from there.
Crossing Qwen and T5 independently
To separate that, I took the “Qwen hidden state (cross-attention context)” and the “T5 token IDs and weights (cross-attention query)” that CLIPTextEncode returns from different prompts, combined them, and generated four combinations plus one extra condition at seed 42.
| Condition | Qwen hidden state | T5 token IDs and weights |
|---|---|---|
| Qwen=P0 / T5=P0 | P0 (trigger only) | P0 |
| Qwen=P3 / T5=P0 | P3 (ID anchors + position lines + outfit line) | P0 |
| Qwen=P0 / T5=P3 | P0 | P3 |
| Qwen=P3 / T5=P3 | P3 | P3 |
| Qwen=all zeros / T5=P3 | All tokens zeroed | P3 |

Qwen=P0/T5=P0 is P0 on both sides, so it produces the same output as feeding P0 normally. kanachan is absent and there are two kuraras.

Qwen=P3/T5=P0 swaps in P3 on the Qwen side only. The output is nearly unchanged from Qwen=P0/T5=P0. With T5 left at P0 (trigger only, 43 effective tokens), putting P3’s ID text on the Qwen side still leaves kanachan absent and two kuraras in place.

Qwen=P0/T5=P3 does the reverse and swaps in P3 (179 effective tokens) on the T5 side only. Even with the Qwen side at P0, kanachan and keichan come out with their ID features. koharu is missing, though, and two kuraras appear instead, so this does not reach all four being drawn apart.

Qwen=P3/T5=P3 is P3 on both sides, the normal P3 generation. All four are drawn apart.

Qwen=all zeros/T5=P3 cuts the Qwen cross-attention input across every token. Even so, kanachan in the middle and keichan on the right come out recognisably. The one on the left is a kurara-ish figure carrying kanachan’s side ponytail, and koharu never appears, leaving three.
One possible reason nothing changed when only the Qwen side moved to P3 while T5 stayed at P0 is that a short T5 sequence has few query positions, leaving little path for Qwen-side information to be reflected. I did not observe cross-attention, though, so the mechanism is unverified.
Swapping the T5 side (the LLMAdapter query, which carries the LLMAdapter’s own embedding independently of Qwen) produces a large change. But P0 and P3 differ in ID text, position lines and outfit line as well as token count, so this comparison cannot settle the effect of token count alone or any threshold.
Across these five conditions at seed 42, only Qwen=P3/T5=P3 (the normal condition) drew all four apart. Zeroing Qwen changes both the count and the cast, so the Qwen path does contribute to the output, but which character it is required for cannot be settled here.
My initial verdict, “what decides the 4-character separation is the LLMAdapter’s processing, not Qwen3-0.6B”, could not be established from interventions A, B and C alone, since those only touched the Qwen side. Here is what the seed 42 cross experiment does establish.
(1) Changing the T5 side from P0 to P3 changes the image substantially
(2) Changing only the Qwen side to P3 while T5 stays at P0 does not bring kanachan back
(3) Zeroing Qwen makes the four-way separation of Qwen=P3/T5=P3 impossible to reproduce
The T5-side sequence content has a strong influence and the Qwen path contributes as well, but the relative size of the two contributions, any token-count threshold, and whether Qwen3-0.6B’s capacity is the bottleneck all remain undecided.
A fixed dictionary alone made all four look like themselves at seed 42
If all you need is to turn a short trigger into the long prompt that works, you can expand it on the generation server without swapping out the text encoder inside Anima. At the same seed I compared three conditions: (1) feeding the trigger words as-is, (2) filling in ID text deterministically from a per-character fixed dictionary, and (3) handing a large Qwen the character dictionary and triggers, having it expand them into per-character sentences, then feeding that to the current Anima.
The large Qwen was given the character dictionary; it was never asked to guess the settings from the custom triggers alone. Its rewrites also showed reordered inputs and dropped attributes.
The “fixed dictionary” in condition 2 is the set of ID anchors for the four characters that the generation server keeps in its character presets, with no position lines and no outfit line. This reproduces the state where I type only a minimal scene description (full body, white background, simple background) and pick a character preset. The “large Qwen” in condition 3 is a local qwen3.6:35b (quantised, 23GB) running under Ollama on an M4 Mac mini. I handed it the per-character feature lists the generation server holds, and told it to output in the same structure as P3 (position lines + one sentence per character + outfit line).
Prompt expansion results
All three conditions were generated at seed 42.
| Condition | All four look like themselves | How features migrated | Position |
|---|---|---|---|
| Trigger only (P0) | ×. kanachan is absent and there are two kuraras | kanachan was replaced wholesale by kurara | Ended up in a school uniform that was never requested |
| Fixed dictionary | ○. All four show their ID features (hairstyle, eye colour) | No features migrated to other characters | From left: kurara → koharu → kanachan → keichan. Without position lines the order does not match the trigger order |
| Expanded by a large Qwen | △. kurara and keichan match. kanachan is absent and there are two keichans | kanachan’s side ponytail and blue scrunchie migrated to both keichan and koharu | From left: kurara, keichan, a keichan-ish figure, koharu. The third is not kanachan |



The fixed dictionary alone (ID anchors only, no position lines, no outfit line) brought out all four ID features at this seed. The boundary test in the previous article never measured 4 characters at Lv1 (ID anchors only). Without position lines, though, the order cannot be controlled. If you need a specific order in production you still need position lines, but for identity alone, “who comes out looking like whom”, adding ID anchors may take Lv0 close to Lv3.
Expanding with a large Qwen failed to produce kanachan at all, despite building a prompt with the same structure as P3. The third figure stayed blonde with a side ponytail, a keichan-ish result, and koharu picked up the same side ponytail and ahoge. Checking the prompt string, the kanachan feature list handed to the large Qwen contains only side ponytail, ahoge, double parted bangs, medium hair, blue scrunchie, and no word for hair colour (brown or otherwise) is actually present. The boundary-test prompt from the previous article (P3 here) had “brown” filled in by hand as with a brown side ponytail and an ahoge. The large Qwen expanded the feature list it was given, but did not invent colour information that was missing from the dictionary. With no colour of her own, kanachan collapsed into a hairstyle specification — side ponytail, blue scrunchie and ahoge — and that specification landed on keichan and koharu. Before model size becomes the accuracy question, the dictionary being handed over had no colour in it.
What this run does and does not settle
| Measurement | Reading |
|---|---|
| Every condition sits under 512 tokens with no truncation | Truncation is not why short prompts fail |
| Operating on a character description region at Qwen’s final layer barely moves the image, while zeroing the same region just before the DiT replaced a character in all 3 seeds | The description region just before the DiT decides who is drawn |
| At seed 42, Qwen=P3/T5=P0 stays on the failing side, while Qwen=P0/T5=P3 brings some characters out with their own features | In this condition, changing the T5 side to P3 had the larger effect |
| Qwen=all zeros/T5=P3 could not reproduce all four being drawn apart | The Qwen path contributes to the output too |
| ID anchors from the fixed dictionary got all four to match at seed 42 | Prompt expansion alone gives a usable control |
The effect of token count on its own, whether the LLMAdapter alone decides the separation, any T5 token-count threshold and how far this generalises to other seeds, whether Qwen3-0.6B has enough capacity, and whether the fixed dictionary holds up across seeds are all undecided by this run.
Why a short 4-character prompt fails is not explained by Qwen3-0.6B’s representational capacity alone. In the seed 42 cross experiment, changing the T5 side from P0 to P3 moved the image more than changing only the Qwen side did. Since P0 and P3 differ in both token count and text content at once, whether length, the ID text, or both changed the image is still unknown.
On the other side, zeroing Qwen changed both the count and the cast compared to normal P3. The Qwen path does contribute to the output, and there is still room to try a larger model. Feeding P0 and P3 into the same 0.6B says nothing about the difference a larger model’s hidden state would make.
In practice, filling in ID anchors from a complete character dictionary keeps everything on the generation server without touching Anima. But this was confirmed once, at seed 42, and the order still cannot be controlled. That the fixed dictionary alone made them look like themselves is useful as a control for measuring whether the next round of internal experiments is really needed.